├── 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 │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ ├── card_cover1.png │ ├── card_cover2.png │ ├── card_cover3.png │ ├── card_cover4.png │ ├── card_cover5.png │ ├── card_cover6.png │ ├── card_cover7.png │ ├── card_cover8.png │ ├── home_card_bg.9.png │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gif ├── 1.gif ├── 2.gif └── 3.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.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-xxhdpi │ ├── ic_loading_1.png │ ├── ic_loading_2.png │ ├── ic_loading_3.png │ ├── loading.png │ ├── loading_1.png │ ├── loading_2.png │ ├── loading_3.png │ ├── loading_4.png │ ├── loading_5.png │ ├── loading_6.png │ ├── loading_7.png │ └── loading_8.png │ ├── 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 /README.md: -------------------------------------------------------------------------------- 1 | :running:HorizontalRefreshLayout-Android:running: 2 | ============ 3 | 4 | 开发者使用 HorizontalRefreshLayout-Android 可以对RecycView、Listview、ScrollView等控件实现左右刷新 5 | 6 | ##  APK下载 7 | [Download](https://github.com/linuxjava/HorizontalRefreshLayout/raw/master/apk/app-debug.apk) 8 | ##  Demo使用 9 | 运行demo需删除gradle.properties中的代理 10 | ```xml 11 | systemProp.http.proxyHost=dev-proxy.oa.com 12 | systemProp.http.proxyPort=8080 13 | systemProp.https.proxyHost=dev-proxy.oa.com 14 | systemProp.https.proxyPort=8080 15 | ``` 16 | ## Gradle配置 17 | compile 'xiao.free.horizontalrefreshlayout:lib:v0.1.2' 18 | ## XML配置 19 | ```xml 20 | 27 | 28 | 36 | 37 | 38 | ``` 39 | ## Java代码 40 | ```java 41 | refreshLayout = (HorizontalRefreshLayout) findViewById(R.id.refresh); 42 | refreshLayout.setRefreshCallback(this); 43 | refreshLayout.setRefreshHeader(new LoadingRefreshHeader(this), HorizontalRefreshLayout.LEFT); 44 | refreshLayout.setRefreshHeader(new LoadingRefreshHeader(this), HorizontalRefreshLayout.RIGHT); 45 | ``` 46 | 通过setRefreshHeader方法可以设置左右刷新头部,库中已支持三种刷新效果,如下图所示: 47 | 48 | ![image](https://github.com/linuxjava/HorizontalRefreshLayout/raw/master/gif/1.gif) 49 | ![image](https://github.com/linuxjava/HorizontalRefreshLayout/raw/master/gif/2.gif) 50 | ![image](https://github.com/linuxjava/HorizontalRefreshLayout/raw/master/gif/3.gif) 51 | 52 | ## 自定义Header 53 | 可通过实现如下接口实现自定义header 54 | ```java 55 | public interface RefreshHeader { 56 | /** 57 | * @param dragPosition HorizontalRefreshLayout.START or HorizontalRefreshLayout.END 58 | */ 59 | void onStart(int dragPosition, View refreshHead); 60 | 61 | /** 62 | * @param distance 63 | */ 64 | void onDragging(float distance, float percent, View refreshHead); 65 | 66 | void onReadyToRelease(View refreshHead); 67 | 68 | @NonNull View getView(ViewGroup container); 69 | 70 | void onRefreshing(View refreshHead); 71 | } 72 | ``` 73 | 具体可参考lib库中refreshhead目录中的实现 74 | -------------------------------------------------------------------------------- /apk/app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/apk/app-debug.apk -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion ANDROID_BUILD_SDK_VERSION as int 5 | buildToolsVersion ANDROID_BUILD_TOOLS_VERSION 6 | defaultConfig { 7 | applicationId "com.example.robincxiao.horizontalrefreshlayout" 8 | minSdkVersion 14 9 | targetSdkVersion ANDROID_BUILD_SDK_VERSION 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(include: ['*.jar'], dir: 'libs') 23 | compile 'com.github.lsjwzh.RecyclerViewPager:lib:v1.1.2' 24 | //compile project(':lib') 25 | compile 'xiao.free.horizontalrefreshlayout:lib:v0.1.2' 26 | //compile 'xiao.free.horizontalrefreshlayout:lib:v1.0.0' 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/robincxiao/horizontalrefreshlayout/LayoutAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Lucas Rocha 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.robincxiao.horizontalrefreshlayout; 18 | 19 | import android.content.Context; 20 | import android.support.v7.widget.RecyclerView; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | import android.widget.ImageView; 25 | import android.widget.TextView; 26 | import android.widget.Toast; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | public class LayoutAdapter extends RecyclerView.Adapter { 32 | private static final int DEFAULT_ITEM_COUNT = 5; 33 | 34 | private final Context mContext; 35 | private final RecyclerView mRecyclerView; 36 | private final List mItems; 37 | private int mCurrentItemId = 0; 38 | private int[] imageIds = {R.mipmap.card_cover1, R.mipmap.card_cover2, R.mipmap.card_cover3, 39 | R.mipmap.card_cover4, R.mipmap.card_cover5, R.mipmap.card_cover6, 40 | R.mipmap.card_cover7, R.mipmap.card_cover8}; 41 | 42 | public static class SimpleViewHolder extends RecyclerView.ViewHolder { 43 | public final TextView title; 44 | public final ImageView myImage; 45 | 46 | public SimpleViewHolder(View view) { 47 | super(view); 48 | title = (TextView) view.findViewById(R.id.title); 49 | myImage = (ImageView) view.findViewById(R.id.image); 50 | } 51 | } 52 | 53 | public LayoutAdapter(Context context, RecyclerView recyclerView) { 54 | this(context, recyclerView, DEFAULT_ITEM_COUNT); 55 | } 56 | 57 | public LayoutAdapter(Context context, RecyclerView recyclerView, int itemCount) { 58 | mContext = context; 59 | mItems = new ArrayList<>(itemCount); 60 | for (int i = 0; i < itemCount; i++) { 61 | addItem(i); 62 | } 63 | 64 | mRecyclerView = recyclerView; 65 | } 66 | 67 | public void addItem(int position) { 68 | final int id = mCurrentItemId++; 69 | mItems.add(position, id); 70 | notifyItemInserted(position); 71 | } 72 | 73 | public void removeItem(int position) { 74 | mItems.remove(position); 75 | notifyItemRemoved(position); 76 | } 77 | 78 | public void getMore() { 79 | int size = getItemCount(); 80 | for (int i = size; i < size + 5; i++) { 81 | addItem(i); 82 | } 83 | } 84 | 85 | @Override 86 | public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 87 | final View view = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false); 88 | return new SimpleViewHolder(view); 89 | } 90 | 91 | @Override 92 | public void onBindViewHolder(SimpleViewHolder holder, int position) { 93 | holder.title.setText("Index " + mItems.get(position)); 94 | holder.myImage.setImageResource(imageIds[position % imageIds.length]); 95 | 96 | final View itemView = holder.itemView; 97 | itemView.setOnClickListener(new View.OnClickListener() { 98 | @Override 99 | public void onClick(View v) { 100 | Toast.makeText(mContext, "", Toast.LENGTH_SHORT).show(); 101 | } 102 | }); 103 | final int itemId = mItems.get(position); 104 | } 105 | 106 | @Override 107 | public int getItemCount() { 108 | return mItems.size(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/robincxiao/horizontalrefreshlayout/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.robincxiao.horizontalrefreshlayout; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | 7 | import com.lsjwzh.widget.recyclerviewpager.RecyclerViewPager; 8 | 9 | import xiao.free.horizontalrefreshlayout.HorizontalRefreshLayout; 10 | import xiao.free.horizontalrefreshlayout.RefreshCallBack; 11 | import xiao.free.horizontalrefreshlayout.refreshhead.LoadingRefreshHeader; 12 | import xiao.free.horizontalrefreshlayout.refreshhead.MaterialRefreshHeader; 13 | import xiao.free.horizontalrefreshlayout.refreshhead.NiceRefreshHeader; 14 | 15 | public class MainActivity extends AppCompatActivity implements RefreshCallBack { 16 | private HorizontalRefreshLayout refreshLayout; 17 | protected RecyclerViewPager mRecyclerView; 18 | private LayoutAdapter mLayoutAdapter; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | refreshLayout = (HorizontalRefreshLayout) findViewById(R.id.refresh); 26 | refreshLayout.setRefreshCallback(this); 27 | refreshLayout.setRefreshHeader(new NiceRefreshHeader(this), HorizontalRefreshLayout.LEFT); 28 | refreshLayout.setRefreshHeader(new NiceRefreshHeader(this), HorizontalRefreshLayout.RIGHT); 29 | 30 | mRecyclerView = (RecyclerViewPager) findViewById(R.id.viewpager); 31 | LinearLayoutManager layout = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); 32 | mRecyclerView.setLayoutManager(layout); 33 | mLayoutAdapter = new LayoutAdapter(this, mRecyclerView); 34 | mRecyclerView.setAdapter(mLayoutAdapter); 35 | mRecyclerView.setHasFixedSize(true); 36 | mRecyclerView.setLongClickable(true); 37 | 38 | mRecyclerView.addOnPageChangedListener(new RecyclerViewPager.OnPageChangedListener() { 39 | @Override 40 | public void OnPageChanged(int oldPosition, int newPosition) { 41 | int size = mLayoutAdapter.getItemCount(); 42 | if (size > 1 && newPosition == size - 1) { 43 | //mLayoutAdapter.getMore(); 44 | //refreshLayout.startAutoRefresh(HorizontalRefreshLayout.RIGHT); 45 | } 46 | } 47 | }); 48 | 49 | //refreshLayout.startAutoRefresh(HorizontalRefreshLayout.LEFT); 50 | } 51 | 52 | @Override 53 | public void onLeftRefreshing() { 54 | refreshLayout.postDelayed(new Runnable() { 55 | @Override 56 | public void run() { 57 | refreshLayout.onRefreshComplete(); 58 | } 59 | }, 2000); 60 | } 61 | 62 | @Override 63 | public void onRightRefreshing() { 64 | refreshLayout.postDelayed(new Runnable() { 65 | @Override 66 | public void run() { 67 | mLayoutAdapter.getMore(); 68 | refreshLayout.onRefreshComplete(); 69 | } 70 | }, 2000); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/item_background.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 15 | 16 | 25 | 26 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/card_cover1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/card_cover1.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/card_cover2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/card_cover2.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/card_cover3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/card_cover3.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/card_cover4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/card_cover4.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/card_cover5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/card_cover5.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/card_cover6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/card_cover6.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/card_cover7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/card_cover7.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/card_cover8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/card_cover8.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/home_card_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/home_card_bg.9.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #CCCCCC 8 | #77CEEE 9 | #ff33b5e5 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | HorizontalRefreshLayout 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath "com.android.tools.build:gradle:$GRADLE_VERSION" 9 | 10 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' 11 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | maven { url "https://jitpack.io" } 20 | jcenter() 21 | } 22 | 23 | tasks.withType(Javadoc).all { 24 | enabled = false 25 | options.setEncoding('UTF-8') 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /gif/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/gif/1.gif -------------------------------------------------------------------------------- /gif/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/gif/2.gif -------------------------------------------------------------------------------- /gif/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/gif/3.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | #gradle版本 19 | GRADLE_VERSION=2.1.0 20 | #目标SDK版本 21 | ANDROID_BUILD_TARGET_SDK_VERSION=19 22 | #编译SDK版本 23 | ANDROID_BUILD_SDK_VERSION=23 24 | #编译工具版本 25 | ANDROID_BUILD_TOOLS_VERSION=23.0.2 26 | #V7包版本 27 | SUPPORT_LIBRARY_VERSION=23.4.0 28 | 29 | #代理 30 | systemProp.http.proxyHost=dev-proxy.oa.com 31 | systemProp.http.proxyPort=8080 32 | systemProp.https.proxyHost=dev-proxy.oa.com 33 | systemProp.https.proxyPort=8080 34 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /jcenter-push.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | def siteUrl = "https://github.com/linuxjava/HorizontalRefreshLayout" 5 | def gitUrl = "https://github.com/linuxjava/HorizontalRefreshLayout.git" 6 | 7 | group = 'xiao.free.horizontalrefreshlayout'// library name 8 | version = 'v0.1.2'// version 9 | 10 | install { 11 | repositories.mavenInstaller { 12 | // This generates POM.xml with proper paramters 13 | pom { 14 | project { 15 | packaging 'aar' 16 | 17 | name 'HorizontalRefreshLayout for Android'//添加项目描述 18 | url siteUrl 19 | 20 | licenses { 21 | license { 22 | name 'The Apache Software License, Version 2.0' 23 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 24 | } 25 | } 26 | 27 | developers { 28 | developer { 29 | id 'guochangxiao' 30 | name 'guochangxiao' 31 | email 'guochangxiao@gmail.com' 32 | } 33 | } 34 | 35 | scm { 36 | connection gitUrl 37 | developerConnection gitUrl 38 | url siteUrl 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | task sourcesJar(type: Jar) { 46 | from android.sourceSets.main.java.srcDirs 47 | classifier = 'sources' 48 | } 49 | 50 | task javadoc(type: Javadoc) { 51 | source = android.sourceSets.main.java.srcDirs 52 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 53 | } 54 | 55 | task javadocJar(type: Jar, dependsOn: javadoc) { 56 | classifier = 'javadoc' 57 | from javadoc.destinationDir 58 | } 59 | 60 | artifacts { 61 | archives javadocJar 62 | archives sourcesJar 63 | } 64 | 65 | Properties properties = new Properties() 66 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 67 | 68 | bintray { 69 | user = properties.getProperty("bintray.user") 70 | key = properties.getProperty("bintray.apikey") 71 | 72 | configurations = ['archives'] 73 | 74 | pkg { 75 | repo = "maven" //JCenter上仓储名 76 | name = "horizontalrefreshlayout" //JCenter上的项目中的packageName 77 | websiteUrl = siteUrl 78 | vcsUrl = gitUrl 79 | licenses = ["Apache-2.0"] 80 | publish = true 81 | } 82 | } -------------------------------------------------------------------------------- /lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion ANDROID_BUILD_SDK_VERSION as int 5 | buildToolsVersion ANDROID_BUILD_TOOLS_VERSION 6 | resourcePrefix "horizontalrefreshlayout_" 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion ANDROID_BUILD_SDK_VERSION 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | compile 'com.android.support:appcompat-v7:23.4.0' 28 | } 29 | 30 | 31 | apply from: '../jcenter-push.gradle' 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/src/main/java/xiao/free/horizontalrefreshlayout/HorizontalRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package xiao.free.horizontalrefreshlayout; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.support.v4.view.ViewCompat; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.util.TypedValue; 10 | import android.view.Gravity; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.ViewConfiguration; 14 | import android.widget.FrameLayout; 15 | 16 | /** 17 | * Created by robincxiao on 2017/2/6. 18 | * 1.View布局问题,onMeasure、onLayout 19 | * 2.滑动冲突问题 20 | * 2.1事件拦截 21 | * 2.2拖动效果 22 | * 3.释放时自动归位问题 23 | * 值得注意的问题: 24 | * 1.onlayout中header初始化 25 | * 2.onTouchEvent的返回值 26 | */ 27 | 28 | public class HorizontalRefreshLayout extends FrameLayout { 29 | private static final int DURATION = 150; 30 | private Context context; 31 | private RefreshHeader leftRefreshHeader; 32 | private RefreshHeader rightRefreshHeader; 33 | private View mTargetView; 34 | private View leftHeaderView; 35 | private View rightHeaderView; 36 | private RefreshCallBack refreshCallback; 37 | private int touchSlop; 38 | private int dragMarginPx; 39 | private int leftHeaderWidth; 40 | private int rightHeaderWidth; 41 | //最大拖动距离 42 | private int dragMaxHeaderWidth; 43 | private int mLastInterceptX; 44 | private int mLastInterceptY; 45 | private int mLastX; 46 | private int mLastY; 47 | 48 | private float mTargetTranslationX = 0; 49 | //header状态,当前显示是左边header、右边header 50 | private int headerState = -1; 51 | public static final int LEFT = 0; 52 | public static final int RIGHT = 1; 53 | //刷新状态 54 | private static final int REFRESH_STATE_IDLE = 0; 55 | private static final int REFRESH_STATE_START = 1; 56 | private static final int REFRESH_STATE_DRAGGING = 2; 57 | private static final int REFRESH_STATE_READY_TO_RELEASE = 3; 58 | private static final int REFRESH_STATE_REFRESHING = 4; 59 | private int refreshState = REFRESH_STATE_IDLE; 60 | 61 | 62 | public HorizontalRefreshLayout(Context context) { 63 | super(context); 64 | 65 | init(); 66 | } 67 | 68 | public HorizontalRefreshLayout(Context context, AttributeSet attrs) { 69 | super(context, attrs); 70 | 71 | init(); 72 | } 73 | 74 | public HorizontalRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { 75 | super(context, attrs, defStyleAttr); 76 | 77 | init(); 78 | } 79 | 80 | public void setRefreshCallback(RefreshCallBack callback) { 81 | refreshCallback = callback; 82 | } 83 | 84 | private void init() { 85 | context = getContext(); 86 | touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 87 | } 88 | 89 | @Override 90 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 91 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 92 | 93 | if(leftHeaderView != null) { 94 | leftHeaderWidth = leftHeaderView.getMeasuredWidth(); 95 | dragMarginPx = (int) (leftHeaderWidth * 0.6); 96 | dragMaxHeaderWidth = leftHeaderWidth + dragMarginPx; 97 | } 98 | 99 | if(rightHeaderView != null) { 100 | rightHeaderWidth = rightHeaderView.getMeasuredWidth(); 101 | if(dragMarginPx == 0){ 102 | dragMarginPx = (int) (rightHeaderWidth * 0.6); 103 | dragMaxHeaderWidth = rightHeaderWidth + dragMarginPx; 104 | } 105 | } 106 | } 107 | 108 | @Override 109 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 110 | if (getChildCount() == 0) { 111 | return; 112 | } 113 | 114 | if (mTargetView == null) { 115 | findTargetView(); 116 | if (mTargetView == null) { 117 | return; 118 | } 119 | } 120 | 121 | /** 122 | * 注意:只有状态是IDLE时才初始化刷新header的TranslationX;因为在滑动mTargetView时onLayout会被重新调用, 123 | * 如果不是在REFRESH_STATE_IDLE状态下设置setTranslationX,则会产生问题 124 | */ 125 | if (refreshState == REFRESH_STATE_IDLE) { 126 | if (leftHeaderView != null) { 127 | leftHeaderView.setTranslationX(-leftHeaderWidth); 128 | } 129 | 130 | if (rightHeaderView != null) { 131 | rightHeaderView.setTranslationX(rightHeaderWidth); 132 | } 133 | } 134 | 135 | super.onLayout(changed, left, top, right, bottom); 136 | } 137 | 138 | private void findTargetView() { 139 | if (mTargetView == null) { 140 | for (int i = 0; i < getChildCount(); i++) { 141 | View child = getChildAt(i); 142 | if (!child.equals(leftHeaderView) && !child.equals(rightHeaderView)) { 143 | mTargetView = child; 144 | break; 145 | } 146 | } 147 | } 148 | } 149 | 150 | @Override 151 | public boolean onInterceptTouchEvent(MotionEvent ev) { 152 | int x = (int) ev.getX(); 153 | int y = (int) ev.getY(); 154 | 155 | switch (ev.getAction()) { 156 | case MotionEvent.ACTION_DOWN: 157 | mLastX = mLastInterceptX = x; 158 | mLastY = mLastInterceptY = y; 159 | break; 160 | case MotionEvent.ACTION_MOVE: 161 | int deltaX = x - mLastInterceptX; 162 | int deltaY = y - mLastInterceptY; 163 | 164 | mLastX = mLastInterceptX = x; 165 | mLastY = mLastInterceptY = y; 166 | 167 | /** 168 | * 注意:需要判断refreshState != REFRESH_STATE_REFRESHING,否则当处于REFRESH_STATE_REFRESHING状态 169 | * 再次拖动滑动时,会有些许小瑕疵 170 | */ 171 | if (Math.abs(deltaX) > Math.abs(deltaY)) {//判断是否是水平滑动 172 | if (leftHeaderView != null && deltaX > 0 && !canChildScrollRight() && refreshState != REFRESH_STATE_REFRESHING) {//手指向右滑动 173 | headerState = LEFT; 174 | refreshState = REFRESH_STATE_START; 175 | leftRefreshHeader.onStart(LEFT, leftHeaderView); 176 | 177 | return true; 178 | } else if (rightHeaderView != null && deltaX < 0 && !canChildScrollLeft() && refreshState != REFRESH_STATE_REFRESHING) {//手指向左滑动 179 | headerState = RIGHT; 180 | refreshState = REFRESH_STATE_START; 181 | rightRefreshHeader.onStart(RIGHT, rightHeaderView); 182 | 183 | return true; 184 | } 185 | } 186 | break; 187 | case MotionEvent.ACTION_UP: 188 | case MotionEvent.ACTION_CANCEL: 189 | mLastInterceptX = 0; 190 | mLastInterceptY = 0; 191 | break; 192 | } 193 | 194 | return super.onInterceptTouchEvent(ev); 195 | } 196 | 197 | @Override 198 | public boolean onTouchEvent(MotionEvent event) { 199 | int x = (int) event.getX(); 200 | int y = (int) event.getY(); 201 | 202 | /** 203 | * 为什么不在onTouchEvent中直接返回true,而是在ACTION_MOVE/ACTION_UP/ACTION_CANCEL返回true,ACTION_DOWN不返回true? 204 | * 在如下场景下需要这样设计:header正在刷新的时候,用户点击在header上开始drag,但是当header正在刷新的时候,我们并不希望 205 | * headerview和mTargetView被拖动,因此需要这样去处理。 206 | */ 207 | switch (event.getAction()) { 208 | case MotionEvent.ACTION_DOWN: 209 | mLastX = x; 210 | mLastY = y; 211 | break; 212 | case MotionEvent.ACTION_MOVE: 213 | int deltaX = x - mLastX; 214 | 215 | mLastX = x; 216 | mLastY = y; 217 | 218 | float dampingDX = deltaX * (1 - Math.abs((mTargetTranslationX / dragMaxHeaderWidth))); //let drag action has resistance 219 | mTargetTranslationX += dampingDX; 220 | 221 | if (headerState == LEFT) { 222 | if (mTargetTranslationX <= 0) { 223 | Log.d("xiao1", "test1"); 224 | mTargetTranslationX = 0; 225 | mTargetView.setTranslationX(0); 226 | } else if (mTargetTranslationX >= dragMaxHeaderWidth) { 227 | Log.d("xiao1", "test2"); 228 | mTargetTranslationX = dragMaxHeaderWidth; 229 | mTargetView.setTranslationX(mTargetTranslationX); 230 | } else { 231 | Log.d("xiao1", "test3"); 232 | 233 | mTargetView.setTranslationX(mTargetTranslationX); 234 | 235 | if (refreshState != REFRESH_STATE_READY_TO_RELEASE && mTargetTranslationX >= leftHeaderWidth) { 236 | refreshState = REFRESH_STATE_READY_TO_RELEASE; 237 | 238 | leftRefreshHeader.onReadyToRelease(leftHeaderView); 239 | } else { 240 | refreshState = REFRESH_STATE_DRAGGING; 241 | //计算出拖动的比率 242 | float percent = Math.abs(mTargetTranslationX / leftHeaderWidth); 243 | leftRefreshHeader.onDragging(mTargetTranslationX, percent, leftHeaderView); 244 | } 245 | } 246 | 247 | leftHeaderView.setTranslationX(-leftHeaderWidth + mTargetTranslationX); 248 | } else if ((headerState == RIGHT)) { 249 | if (mTargetTranslationX >= 0) { 250 | Log.d("xiao1", "test4"); 251 | mTargetTranslationX = 0; 252 | mTargetView.setTranslationX(0); 253 | } else if (mTargetTranslationX <= -dragMaxHeaderWidth) { 254 | Log.d("xiao1", "test5"); 255 | mTargetTranslationX = -dragMaxHeaderWidth; 256 | mTargetView.setTranslationX(mTargetTranslationX); 257 | } else { 258 | Log.d("xiao1", "test6"); 259 | mTargetView.setTranslationX(mTargetTranslationX); 260 | 261 | if (refreshState != REFRESH_STATE_READY_TO_RELEASE && mTargetTranslationX <= -rightHeaderWidth) { 262 | refreshState = REFRESH_STATE_READY_TO_RELEASE; 263 | rightRefreshHeader.onReadyToRelease(rightHeaderView); 264 | } else { 265 | refreshState = REFRESH_STATE_DRAGGING; 266 | //计算出拖动的比率 267 | float percent = Math.abs(mTargetTranslationX / rightHeaderWidth); 268 | rightRefreshHeader.onDragging(mTargetTranslationX, percent, rightHeaderView); 269 | } 270 | } 271 | 272 | rightHeaderView.setTranslationX(rightHeaderWidth + mTargetTranslationX); 273 | } 274 | return true; 275 | case MotionEvent.ACTION_UP: 276 | case MotionEvent.ACTION_CANCEL: 277 | mLastX = mLastInterceptX = 0; 278 | mLastY = mLastInterceptY = 0; 279 | 280 | if (headerState == LEFT) { 281 | if (mTargetTranslationX < leftHeaderWidth) { 282 | Log.d("xiao1", "test7"); 283 | smoothRelease(); 284 | } else { 285 | Log.d("xiao1", "test8"); 286 | smoothLocateToRefresh(); 287 | } 288 | } else if (headerState == RIGHT) { 289 | if (mTargetTranslationX > -rightHeaderWidth) { 290 | Log.d("xiao1", "test9"); 291 | smoothRelease(); 292 | } else { 293 | Log.d("xiao1", "test10"); 294 | smoothLocateToRefresh(); 295 | } 296 | } 297 | return true; 298 | } 299 | 300 | return super.onTouchEvent(event); 301 | } 302 | 303 | /** 304 | * 释放滑动 305 | */ 306 | private void smoothRelease() { 307 | mTargetView.animate().translationX(0).setDuration(DURATION) 308 | .setListener(new AnimatorListenerAdapter() { 309 | @Override 310 | public void onAnimationEnd(Animator animation) { 311 | //动画结束后reset状态 312 | refreshState = REFRESH_STATE_IDLE; 313 | headerState = -1; 314 | mTargetTranslationX = 0; 315 | } 316 | }) 317 | .start(); 318 | 319 | if (headerState == LEFT) { 320 | if (leftHeaderView != null) { 321 | leftRefreshHeader.onStart(LEFT, leftHeaderView);//恢复到开始状态 322 | leftHeaderView.animate().translationX(-leftHeaderWidth).setDuration(DURATION).start(); 323 | } 324 | } else if (headerState == RIGHT) { 325 | if (rightHeaderView != null) { 326 | rightRefreshHeader.onStart(LEFT, rightHeaderView);//恢复到开始状态 327 | rightHeaderView.animate().translationX(rightHeaderWidth).setDuration(DURATION).start(); 328 | } 329 | } 330 | } 331 | 332 | /** 333 | * 滑动到刷新位置 334 | */ 335 | private void smoothLocateToRefresh() { 336 | if (headerState == LEFT && leftHeaderView != null) { 337 | refreshState = REFRESH_STATE_REFRESHING; 338 | 339 | leftHeaderView.animate().translationX(0).setDuration(DURATION).start(); 340 | 341 | leftRefreshHeader.onRefreshing(leftHeaderView);//正在刷新 342 | 343 | mTargetView.animate().translationX(leftHeaderWidth).setDuration(DURATION) 344 | .setListener(new AnimatorListenerAdapter() { 345 | @Override 346 | public void onAnimationEnd(Animator animation) { 347 | mTargetTranslationX = leftHeaderWidth; 348 | 349 | if (refreshCallback != null) { 350 | if (headerState == LEFT) { 351 | refreshCallback.onLeftRefreshing(); 352 | } else { 353 | refreshCallback.onRightRefreshing(); 354 | } 355 | } 356 | } 357 | }) 358 | .start(); 359 | } else if (headerState == RIGHT && rightHeaderView != null) { 360 | refreshState = REFRESH_STATE_REFRESHING; 361 | //注意,这里使用的translationXBy 362 | rightHeaderView.animate().translationXBy(-mTargetTranslationX - rightHeaderWidth).setDuration(DURATION).start(); 363 | 364 | rightRefreshHeader.onRefreshing(rightHeaderView);//正在刷新 365 | 366 | mTargetView.animate().translationX(-rightHeaderWidth).setDuration(DURATION) 367 | .setListener(new AnimatorListenerAdapter() { 368 | @Override 369 | public void onAnimationEnd(Animator animation) { 370 | if (refreshCallback != null) { 371 | if (headerState == LEFT) { 372 | refreshCallback.onLeftRefreshing(); 373 | } else { 374 | refreshCallback.onRightRefreshing(); 375 | } 376 | } 377 | 378 | mTargetTranslationX = -rightHeaderWidth; 379 | } 380 | }) 381 | .start(); 382 | } 383 | } 384 | 385 | /** 386 | * 刷新完成 387 | */ 388 | public void onRefreshComplete() { 389 | smoothRelease(); 390 | } 391 | 392 | /** 393 | * 自动刷新 394 | * 395 | * @param leftOrRight HorizontalRefreshLayout.LEFT or HorizontalRefreshLayout.RIGHT 396 | */ 397 | public void startAutoRefresh(final int leftOrRight) { 398 | // delay to let the animation smoothly 399 | //此处需要采用postDelayed将消息方式view的消息队列中,如果直接调用smoothLocateToRefresh可能view还没能完全初始化好,导致mTarget为null 400 | postDelayed(new Runnable() { 401 | @Override 402 | public void run() { 403 | headerState = leftOrRight; 404 | smoothLocateToRefresh(); 405 | } 406 | }, 100); 407 | } 408 | 409 | private void setLeftHeadView(View view) { 410 | leftHeaderView = view; 411 | ((LayoutParams) leftHeaderView.getLayoutParams()).gravity = Gravity.START; 412 | addView(leftHeaderView, 0); 413 | } 414 | 415 | private void setRightHeadView(View view) { 416 | rightHeaderView = view; 417 | ((LayoutParams) rightHeaderView.getLayoutParams()).gravity = Gravity.END; 418 | addView(rightHeaderView, 0); 419 | } 420 | 421 | /** 422 | * 设置刷新header 423 | * @param header 424 | * @param startOrEnd 425 | */ 426 | public void setRefreshHeader(RefreshHeader header, int startOrEnd) { 427 | if (startOrEnd == LEFT) { 428 | leftRefreshHeader = header; 429 | setLeftHeadView(leftRefreshHeader.getView(this)); 430 | } else if (startOrEnd == RIGHT) { 431 | rightRefreshHeader = header; 432 | setRightHeadView(rightRefreshHeader.getView(this)); 433 | } 434 | } 435 | 436 | /** 437 | * mTargetView是否还能向右滑动 438 | * 439 | * @return 440 | */ 441 | public boolean canChildScrollRight() { 442 | return ViewCompat.canScrollHorizontally(mTargetView, -1); 443 | } 444 | 445 | /** 446 | * mTargetView是否还能向左滑动 447 | * 448 | * @return 449 | */ 450 | public boolean canChildScrollLeft() { 451 | return ViewCompat.canScrollHorizontally(mTargetView, 1); 452 | } 453 | 454 | 455 | public static int dp2px(Context context, float dpVal) { 456 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /lib/src/main/java/xiao/free/horizontalrefreshlayout/RefreshCallBack.java: -------------------------------------------------------------------------------- 1 | package xiao.free.horizontalrefreshlayout; 2 | 3 | /** 4 | * Created by wangqi on 2015/12/24. 5 | */ 6 | public interface RefreshCallBack { 7 | void onLeftRefreshing(); 8 | void onRightRefreshing(); 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/main/java/xiao/free/horizontalrefreshlayout/RefreshHeader.java: -------------------------------------------------------------------------------- 1 | package xiao.free.horizontalrefreshlayout; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | /** 8 | * Created by wangqi on 2015/12/24. 9 | */ 10 | public interface RefreshHeader { 11 | 12 | /** 13 | * @param dragPosition HorizontalRefreshLayout.START or HorizontalRefreshLayout.END 14 | */ 15 | void onStart(int dragPosition, View refreshHead); 16 | 17 | /** 18 | * @param distance 19 | */ 20 | void onDragging(float distance, float percent, View refreshHead); 21 | 22 | void onReadyToRelease(View refreshHead); 23 | 24 | @NonNull View getView(ViewGroup container); 25 | 26 | void onRefreshing(View refreshHead); 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/main/java/xiao/free/horizontalrefreshlayout/refreshhead/LoadingRefreshHeader.java: -------------------------------------------------------------------------------- 1 | package xiao.free.horizontalrefreshlayout.refreshhead; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | import android.widget.ProgressBar; 10 | 11 | import xiao.free.horizontalrefreshlayout.R; 12 | import xiao.free.horizontalrefreshlayout.RefreshHeader; 13 | 14 | /** 15 | * Created by xiaoguochang on 2015/12/24. 16 | */ 17 | public class LoadingRefreshHeader implements RefreshHeader { 18 | private final Context context; 19 | private ProgressBar progressBar; 20 | private ImageView staticLoading; 21 | 22 | public LoadingRefreshHeader(Context context) { 23 | this.context = context; 24 | } 25 | 26 | @NonNull 27 | @Override 28 | public View getView(ViewGroup container) { 29 | View view = LayoutInflater.from(context).inflate(R.layout.common_loading_refresh_header, container, false); 30 | progressBar = (ProgressBar) view.findViewById(R.id.progressbar); 31 | staticLoading = (ImageView) view.findViewById(R.id.static_loading); 32 | progressBar.setVisibility(View.INVISIBLE); 33 | 34 | return view; 35 | } 36 | 37 | @Override 38 | public void onStart(int dragPosition, View refreshHead) { 39 | staticLoading.setVisibility(View.VISIBLE); 40 | progressBar.setVisibility(View.INVISIBLE); 41 | } 42 | 43 | @Override 44 | public void onDragging(float distance, float percent, View refreshHead) { 45 | staticLoading.setRotation(percent * 360); 46 | } 47 | 48 | @Override 49 | public void onReadyToRelease(View refreshHead) { 50 | 51 | } 52 | 53 | @Override 54 | public void onRefreshing(View refreshHead) { 55 | staticLoading.setVisibility(View.INVISIBLE); 56 | progressBar.setVisibility(View.VISIBLE); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/main/java/xiao/free/horizontalrefreshlayout/refreshhead/MaterialRefreshHeader.java: -------------------------------------------------------------------------------- 1 | package xiao.free.horizontalrefreshlayout.refreshhead; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.view.Gravity; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.FrameLayout; 9 | 10 | import xiao.free.horizontalrefreshlayout.HorizontalRefreshLayout; 11 | import xiao.free.horizontalrefreshlayout.R; 12 | import xiao.free.horizontalrefreshlayout.RefreshHeader; 13 | import xiao.free.horizontalrefreshlayout.widget.CircleImageView; 14 | import xiao.free.horizontalrefreshlayout.widget.MaterialProgressDrawable; 15 | 16 | 17 | /** 18 | * Created by wangqi on 2016/7/21. 19 | */ 20 | public class MaterialRefreshHeader implements RefreshHeader { 21 | 22 | private final int startOrEnd; 23 | private CircleImageView mCircleView; 24 | private MaterialProgressDrawable mProgress; 25 | private ViewGroup parent; 26 | 27 | public MaterialRefreshHeader(int startOrEnd) { 28 | this.startOrEnd = startOrEnd; 29 | } 30 | 31 | @Override 32 | public void onStart(int dragPosition, View refreshHead) { 33 | mProgress.stop(); 34 | mProgress.showArrow(false); 35 | mProgress.setAlpha(0); 36 | mProgress.setStartEndTrim(0f, 0f); 37 | } 38 | 39 | @Override 40 | public void onDragging(float distance, float percent, View refreshHead) { 41 | mProgress.showArrow(true); 42 | mProgress.setAlpha((int) (percent * 255)); 43 | mProgress.setProgressRotation(percent); 44 | mProgress.setStartEndTrim(0f, Math.min(.8f, percent)); 45 | } 46 | 47 | @Override 48 | public void onReadyToRelease(View refreshHead) { 49 | } 50 | 51 | @NonNull 52 | @Override 53 | public View getView(ViewGroup container) { 54 | this.parent = container; 55 | ViewGroup view = (ViewGroup) LayoutInflater.from(container.getContext()).inflate(R.layout.material_refresh_header, container, false); 56 | mCircleView = new CircleImageView(container.getContext(), 0xFFFAFAFA, 40 / 2); 57 | mProgress = new MaterialProgressDrawable(container.getContext(), container); 58 | mProgress.setBackgroundColor(0xFFFAFAFA); 59 | mCircleView.setImageDrawable(mProgress); 60 | FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 61 | if (startOrEnd == HorizontalRefreshLayout.LEFT) { 62 | layoutParams.gravity = Gravity.CENTER; 63 | } else { 64 | layoutParams.gravity = Gravity.CENTER; 65 | } 66 | mCircleView.setLayoutParams(layoutParams); 67 | view.addView(mCircleView); 68 | return view; 69 | } 70 | 71 | @Override 72 | public void onRefreshing(View refreshHead) { 73 | mProgress.showArrow(true); 74 | mProgress.setAlpha(255); 75 | mProgress.setProgressRotation(1); 76 | mProgress.setStartEndTrim(0f, Math.min(.8f, 1)); 77 | mProgress.start(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/main/java/xiao/free/horizontalrefreshlayout/refreshhead/NiceRefreshHeader.java: -------------------------------------------------------------------------------- 1 | package xiao.free.horizontalrefreshlayout.refreshhead; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | import android.widget.ProgressBar; 10 | 11 | import xiao.free.horizontalrefreshlayout.R; 12 | import xiao.free.horizontalrefreshlayout.RefreshHeader; 13 | 14 | /** 15 | * Created by xiaoguochang on 2015/12/24. 16 | */ 17 | public class NiceRefreshHeader implements RefreshHeader { 18 | private final Context context; 19 | private ProgressBar progressBar; 20 | private ImageView staticLoading; 21 | 22 | public NiceRefreshHeader(Context context) { 23 | this.context = context; 24 | } 25 | 26 | @NonNull 27 | @Override 28 | public View getView(ViewGroup container) { 29 | View view = LayoutInflater.from(context).inflate(R.layout.nice_refresh_header, container, false); 30 | progressBar = (ProgressBar) view.findViewById(R.id.progressbar); 31 | staticLoading = (ImageView) view.findViewById(R.id.static_loading); 32 | progressBar.setVisibility(View.INVISIBLE); 33 | 34 | return view; 35 | } 36 | 37 | @Override 38 | public void onStart(int dragPosition, View refreshHead) { 39 | staticLoading.setVisibility(View.VISIBLE); 40 | progressBar.setVisibility(View.INVISIBLE); 41 | } 42 | 43 | @Override 44 | public void onDragging(float distance, float percent, View refreshHead) { 45 | int num = (int) (percent * 10); 46 | 47 | switch (num){ 48 | case 1: 49 | case 4: 50 | case 7: 51 | staticLoading.setBackgroundResource(R.drawable.ic_loading_1); 52 | break; 53 | case 2: 54 | case 5: 55 | case 8: 56 | staticLoading.setBackgroundResource(R.drawable.ic_loading_2); 57 | break; 58 | case 3: 59 | case 6: 60 | case 9: 61 | staticLoading.setBackgroundResource(R.drawable.ic_loading_3); 62 | break; 63 | } 64 | } 65 | 66 | @Override 67 | public void onReadyToRelease(View refreshHead) { 68 | 69 | } 70 | 71 | @Override 72 | public void onRefreshing(View refreshHead) { 73 | staticLoading.setVisibility(View.INVISIBLE); 74 | progressBar.setVisibility(View.VISIBLE); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/src/main/java/xiao/free/horizontalrefreshlayout/widget/CircleImageView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package xiao.free.horizontalrefreshlayout.widget; 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.graphics.Color; 22 | import android.graphics.Paint; 23 | import android.graphics.RadialGradient; 24 | import android.graphics.Shader; 25 | import android.graphics.drawable.ShapeDrawable; 26 | import android.graphics.drawable.shapes.OvalShape; 27 | import android.support.v4.view.ViewCompat; 28 | import android.view.animation.Animation; 29 | import android.widget.ImageView; 30 | 31 | /** 32 | * Private class created to work around issues with AnimationListeners being 33 | * called before the animation is actually complete and support shadows on older 34 | * platforms. 35 | */ 36 | public class CircleImageView extends ImageView { 37 | 38 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 39 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 40 | // PX 41 | private static final float X_OFFSET = 0f; 42 | private static final float Y_OFFSET = 1.75f; 43 | private static final float SHADOW_RADIUS = 3.5f; 44 | private static final int SHADOW_ELEVATION = 4; 45 | 46 | private Animation.AnimationListener mListener; 47 | private int mShadowRadius; 48 | 49 | public CircleImageView(Context context, int color, final float radius) { 50 | super(context); 51 | final float density = getContext().getResources().getDisplayMetrics().density; 52 | final int diameter = (int) (radius * density * 2); 53 | final int shadowYOffset = (int) (density * Y_OFFSET); 54 | final int shadowXOffset = (int) (density * X_OFFSET); 55 | 56 | mShadowRadius = (int) (density * SHADOW_RADIUS); 57 | 58 | ShapeDrawable circle; 59 | if (elevationSupported()) { 60 | circle = new ShapeDrawable(new OvalShape()); 61 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 62 | } else { 63 | OvalShape oval = new OvalShadow(mShadowRadius, diameter); 64 | circle = new ShapeDrawable(oval); 65 | ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); 66 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 67 | KEY_SHADOW_COLOR); 68 | final int padding = mShadowRadius; 69 | // set padding so the inner image sits correctly within the shadow. 70 | setPadding(padding, padding, padding, padding); 71 | } 72 | circle.getPaint().setColor(color); 73 | setBackgroundDrawable(circle); 74 | } 75 | 76 | private boolean elevationSupported() { 77 | return android.os.Build.VERSION.SDK_INT >= 21; 78 | } 79 | 80 | @Override 81 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 82 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 83 | if (!elevationSupported()) { 84 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight() 85 | + mShadowRadius*2); 86 | } 87 | } 88 | 89 | public void setAnimationListener(Animation.AnimationListener listener) { 90 | mListener = listener; 91 | } 92 | 93 | @Override 94 | public void onAnimationStart() { 95 | super.onAnimationStart(); 96 | if (mListener != null) { 97 | mListener.onAnimationStart(getAnimation()); 98 | } 99 | } 100 | 101 | @Override 102 | public void onAnimationEnd() { 103 | super.onAnimationEnd(); 104 | if (mListener != null) { 105 | mListener.onAnimationEnd(getAnimation()); 106 | } 107 | } 108 | 109 | /** 110 | * Update the background color of the circle image view. 111 | * 112 | * @param colorRes Id of a color resource. 113 | */ 114 | public void setBackgroundColorRes(int colorRes) { 115 | setBackgroundColor(getContext().getResources().getColor(colorRes)); 116 | } 117 | 118 | @Override 119 | public void setBackgroundColor(int color) { 120 | if (getBackground() instanceof ShapeDrawable) { 121 | ((ShapeDrawable) getBackground()).getPaint().setColor(color); 122 | } 123 | } 124 | 125 | private class OvalShadow extends OvalShape { 126 | private RadialGradient mRadialGradient; 127 | private Paint mShadowPaint; 128 | private int mCircleDiameter; 129 | 130 | public OvalShadow(int shadowRadius, int circleDiameter) { 131 | super(); 132 | mShadowPaint = new Paint(); 133 | mShadowRadius = shadowRadius; 134 | mCircleDiameter = circleDiameter; 135 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, 136 | mShadowRadius, new int[] { 137 | FILL_SHADOW_COLOR, Color.TRANSPARENT 138 | }, null, Shader.TileMode.CLAMP); 139 | mShadowPaint.setShader(mRadialGradient); 140 | } 141 | 142 | @Override 143 | public void draw(Canvas canvas, Paint paint) { 144 | final int viewWidth = CircleImageView.this.getWidth(); 145 | final int viewHeight = CircleImageView.this.getHeight(); 146 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius), 147 | mShadowPaint); 148 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lib/src/main/java/xiao/free/horizontalrefreshlayout/widget/MaterialProgressDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package xiao.free.horizontalrefreshlayout.widget; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.graphics.Canvas; 22 | import android.graphics.Color; 23 | import android.graphics.ColorFilter; 24 | import android.graphics.Paint; 25 | import android.graphics.Paint.Style; 26 | import android.graphics.Path; 27 | import android.graphics.PixelFormat; 28 | import android.graphics.Rect; 29 | import android.graphics.RectF; 30 | import android.graphics.drawable.Animatable; 31 | import android.graphics.drawable.Drawable; 32 | import android.support.annotation.IntDef; 33 | import android.support.annotation.NonNull; 34 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 35 | import android.util.DisplayMetrics; 36 | import android.view.View; 37 | import android.view.animation.Animation; 38 | import android.view.animation.Interpolator; 39 | import android.view.animation.LinearInterpolator; 40 | import android.view.animation.Transformation; 41 | 42 | import java.lang.annotation.Retention; 43 | import java.lang.annotation.RetentionPolicy; 44 | import java.util.ArrayList; 45 | 46 | /** 47 | * Fancy progress indicator for Material theme. 48 | */ 49 | public class MaterialProgressDrawable extends Drawable implements Animatable { 50 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 51 | private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 52 | 53 | private static final float FULL_ROTATION = 1080.0f; 54 | @Retention(RetentionPolicy.CLASS) 55 | @IntDef({LARGE, DEFAULT}) 56 | public @interface ProgressDrawableSize {} 57 | // Maps to ProgressBar.Large style 58 | static final int LARGE = 0; 59 | // Maps to ProgressBar default style 60 | static final int DEFAULT = 1; 61 | 62 | // Maps to ProgressBar default style 63 | private static final int CIRCLE_DIAMETER = 40; 64 | private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 65 | private static final float STROKE_WIDTH = 2.5f; 66 | 67 | // Maps to ProgressBar.Large style 68 | private static final int CIRCLE_DIAMETER_LARGE = 56; 69 | private static final float CENTER_RADIUS_LARGE = 12.5f; 70 | private static final float STROKE_WIDTH_LARGE = 3f; 71 | 72 | private final int[] COLORS = new int[] { 73 | Color.BLACK 74 | }; 75 | 76 | /** 77 | * The value in the linear interpolator for animating the drawable at which 78 | * the color transition should start 79 | */ 80 | private static final float COLOR_START_DELAY_OFFSET = 0.75f; 81 | private static final float END_TRIM_START_DELAY_OFFSET = 0.5f; 82 | private static final float START_TRIM_DURATION_OFFSET = 0.5f; 83 | 84 | /** The duration of a single progress spin in milliseconds. */ 85 | private static final int ANIMATION_DURATION = 1332; 86 | 87 | /** The number of points in the progress "star". */ 88 | private static final float NUM_POINTS = 5f; 89 | /** The list of animators operating on this drawable. */ 90 | private final ArrayList mAnimators = new ArrayList(); 91 | 92 | /** The indicator ring, used to manage animation state. */ 93 | private final Ring mRing; 94 | 95 | /** Canvas rotation in degrees. */ 96 | private float mRotation; 97 | 98 | /** Layout info for the arrowhead in dp */ 99 | private static final int ARROW_WIDTH = 10; 100 | private static final int ARROW_HEIGHT = 5; 101 | private static final float ARROW_OFFSET_ANGLE = 5; 102 | 103 | /** Layout info for the arrowhead for the large spinner in dp */ 104 | private static final int ARROW_WIDTH_LARGE = 12; 105 | private static final int ARROW_HEIGHT_LARGE = 6; 106 | private static final float MAX_PROGRESS_ARC = .8f; 107 | 108 | private Resources mResources; 109 | private View mParent; 110 | private Animation mAnimation; 111 | private float mRotationCount; 112 | private double mWidth; 113 | private double mHeight; 114 | boolean mFinishing; 115 | 116 | public MaterialProgressDrawable(Context context, View parent) { 117 | mParent = parent; 118 | mResources = context.getResources(); 119 | 120 | mRing = new Ring(mCallback); 121 | mRing.setColors(COLORS); 122 | 123 | updateSizes(DEFAULT); 124 | setupAnimators(); 125 | } 126 | 127 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 128 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 129 | final Ring ring = mRing; 130 | final DisplayMetrics metrics = mResources.getDisplayMetrics(); 131 | final float screenDensity = metrics.density; 132 | 133 | mWidth = progressCircleWidth * screenDensity; 134 | mHeight = progressCircleHeight * screenDensity; 135 | ring.setStrokeWidth((float) strokeWidth * screenDensity); 136 | ring.setCenterRadius(centerRadius * screenDensity); 137 | ring.setColorIndex(0); 138 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 139 | ring.setInsets((int) mWidth, (int) mHeight); 140 | } 141 | 142 | /** 143 | * Set the overall size for the progress spinner. This updates the radius 144 | * and stroke width of the ring. 145 | * 146 | * @param size One of {@link MaterialProgressDrawable.LARGE} or 147 | * {@link MaterialProgressDrawable.DEFAULT} 148 | */ 149 | public void updateSizes(@ProgressDrawableSize int size) { 150 | if (size == LARGE) { 151 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 152 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 153 | } else { 154 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 155 | ARROW_WIDTH, ARROW_HEIGHT); 156 | } 157 | } 158 | 159 | /** 160 | * @param show Set to true to display the arrowhead on the progress spinner. 161 | */ 162 | public void showArrow(boolean show) { 163 | mRing.setShowArrow(show); 164 | } 165 | 166 | /** 167 | * @param scale Set the scale of the arrowhead for the spinner. 168 | */ 169 | public void setArrowScale(float scale) { 170 | mRing.setArrowScale(scale); 171 | } 172 | 173 | /** 174 | * Set the start and end trim for the progress spinner arc. 175 | * 176 | * @param startAngle start angle 177 | * @param endAngle end angle 178 | */ 179 | public void setStartEndTrim(float startAngle, float endAngle) { 180 | mRing.setStartTrim(startAngle); 181 | mRing.setEndTrim(endAngle); 182 | } 183 | 184 | /** 185 | * Set the amount of rotation to apply to the progress spinner. 186 | * 187 | * @param rotation Rotation is from [0..1] 188 | */ 189 | public void setProgressRotation(float rotation) { 190 | mRing.setRotation(rotation); 191 | } 192 | 193 | /** 194 | * Update the background color of the circle image view. 195 | */ 196 | public void setBackgroundColor(int color) { 197 | mRing.setBackgroundColor(color); 198 | } 199 | 200 | /** 201 | * Set the colors used in the progress animation from color resources. 202 | * The first color will also be the color of the bar that grows in response 203 | * to a user swipe gesture. 204 | * 205 | * @param colors 206 | */ 207 | public void setColorSchemeColors(int... colors) { 208 | mRing.setColors(colors); 209 | mRing.setColorIndex(0); 210 | } 211 | 212 | @Override 213 | public int getIntrinsicHeight() { 214 | return (int) mHeight; 215 | } 216 | 217 | @Override 218 | public int getIntrinsicWidth() { 219 | return (int) mWidth; 220 | } 221 | 222 | @Override 223 | public void draw(Canvas c) { 224 | final Rect bounds = getBounds(); 225 | final int saveCount = c.save(); 226 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 227 | mRing.draw(c, bounds); 228 | c.restoreToCount(saveCount); 229 | } 230 | 231 | @Override 232 | public void setAlpha(int alpha) { 233 | mRing.setAlpha(alpha); 234 | } 235 | 236 | public int getAlpha() { 237 | return mRing.getAlpha(); 238 | } 239 | 240 | @Override 241 | public void setColorFilter(ColorFilter colorFilter) { 242 | mRing.setColorFilter(colorFilter); 243 | } 244 | 245 | @SuppressWarnings("unused") 246 | void setRotation(float rotation) { 247 | mRotation = rotation; 248 | invalidateSelf(); 249 | } 250 | 251 | @SuppressWarnings("unused") 252 | private float getRotation() { 253 | return mRotation; 254 | } 255 | 256 | @Override 257 | public int getOpacity() { 258 | return PixelFormat.TRANSLUCENT; 259 | } 260 | 261 | @Override 262 | public boolean isRunning() { 263 | final ArrayList animators = mAnimators; 264 | final int N = animators.size(); 265 | for (int i = 0; i < N; i++) { 266 | final Animation animator = animators.get(i); 267 | if (animator.hasStarted() && !animator.hasEnded()) { 268 | return true; 269 | } 270 | } 271 | return false; 272 | } 273 | 274 | @Override 275 | public void start() { 276 | mAnimation.reset(); 277 | mRing.storeOriginals(); 278 | // Already showing some part of the ring 279 | if (mRing.getEndTrim() != mRing.getStartTrim()) { 280 | mFinishing = true; 281 | mAnimation.setDuration(ANIMATION_DURATION/2); 282 | mParent.startAnimation(mAnimation); 283 | } else { 284 | mRing.setColorIndex(0); 285 | mRing.resetOriginals(); 286 | mAnimation.setDuration(ANIMATION_DURATION); 287 | mParent.startAnimation(mAnimation); 288 | } 289 | } 290 | 291 | @Override 292 | public void stop() { 293 | mParent.clearAnimation(); 294 | setRotation(0); 295 | mRing.setShowArrow(false); 296 | mRing.setColorIndex(0); 297 | mRing.resetOriginals(); 298 | } 299 | 300 | private float getMinProgressArc(Ring ring) { 301 | return (float) Math.toRadians( 302 | ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius())); 303 | } 304 | 305 | // Adapted from ArgbEvaluator.java 306 | private int evaluateColorChange(float fraction, int startValue, int endValue) { 307 | int startInt = (Integer) startValue; 308 | int startA = (startInt >> 24) & 0xff; 309 | int startR = (startInt >> 16) & 0xff; 310 | int startG = (startInt >> 8) & 0xff; 311 | int startB = startInt & 0xff; 312 | 313 | int endInt = (Integer) endValue; 314 | int endA = (endInt >> 24) & 0xff; 315 | int endR = (endInt >> 16) & 0xff; 316 | int endG = (endInt >> 8) & 0xff; 317 | int endB = endInt & 0xff; 318 | 319 | return (int)((startA + (int)(fraction * (endA - startA))) << 24) | 320 | (int)((startR + (int)(fraction * (endR - startR))) << 16) | 321 | (int)((startG + (int)(fraction * (endG - startG))) << 8) | 322 | (int)((startB + (int)(fraction * (endB - startB)))); 323 | } 324 | 325 | /** 326 | * Update the ring color if this is within the last 25% of the animation. 327 | * The new ring color will be a translation from the starting ring color to 328 | * the next color. 329 | */ 330 | private void updateRingColor(float interpolatedTime, Ring ring) { 331 | if (interpolatedTime > COLOR_START_DELAY_OFFSET) { 332 | // scale the interpolatedTime so that the full 333 | // transformation from 0 - 1 takes place in the 334 | // remaining time 335 | ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET) 336 | / (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(), 337 | ring.getNextColor())); 338 | } 339 | } 340 | 341 | private void applyFinishTranslation(float interpolatedTime, Ring ring) { 342 | // shrink back down and complete a full rotation before 343 | // starting other circles 344 | // Rotation goes between [0..1]. 345 | updateRingColor(interpolatedTime, ring); 346 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC) 347 | + 1f); 348 | final float minProgressArc = getMinProgressArc(ring); 349 | final float startTrim = ring.getStartingStartTrim() 350 | + (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim()) 351 | * interpolatedTime; 352 | ring.setStartTrim(startTrim); 353 | ring.setEndTrim(ring.getStartingEndTrim()); 354 | final float rotation = ring.getStartingRotation() 355 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 356 | ring.setRotation(rotation); 357 | } 358 | 359 | private void setupAnimators() { 360 | final Ring ring = mRing; 361 | final Animation animation = new Animation() { 362 | @Override 363 | public void applyTransformation(float interpolatedTime, Transformation t) { 364 | if (mFinishing) { 365 | applyFinishTranslation(interpolatedTime, ring); 366 | } else { 367 | // The minProgressArc is calculated from 0 to create an 368 | // angle that matches the stroke width. 369 | final float minProgressArc = getMinProgressArc(ring); 370 | final float startingEndTrim = ring.getStartingEndTrim(); 371 | final float startingTrim = ring.getStartingStartTrim(); 372 | final float startingRotation = ring.getStartingRotation(); 373 | 374 | updateRingColor(interpolatedTime, ring); 375 | 376 | // Moving the start trim only occurs in the first 50% of a 377 | // single ring animation 378 | if (interpolatedTime <= START_TRIM_DURATION_OFFSET) { 379 | // scale the interpolatedTime so that the full 380 | // transformation from 0 - 1 takes place in the 381 | // remaining time 382 | final float scaledTime = (interpolatedTime) 383 | / (1.0f - START_TRIM_DURATION_OFFSET); 384 | final float startTrim = startingTrim 385 | + ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR 386 | .getInterpolation(scaledTime)); 387 | ring.setStartTrim(startTrim); 388 | } 389 | 390 | // Moving the end trim starts after 50% of a single ring 391 | // animation completes 392 | if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) { 393 | // scale the interpolatedTime so that the full 394 | // transformation from 0 - 1 takes place in the 395 | // remaining time 396 | final float minArc = MAX_PROGRESS_ARC - minProgressArc; 397 | float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET) 398 | / (1.0f - START_TRIM_DURATION_OFFSET); 399 | final float endTrim = startingEndTrim 400 | + (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime)); 401 | ring.setEndTrim(endTrim); 402 | } 403 | 404 | final float rotation = startingRotation + (0.25f * interpolatedTime); 405 | ring.setRotation(rotation); 406 | 407 | float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime) 408 | + (FULL_ROTATION * (mRotationCount / NUM_POINTS)); 409 | setRotation(groupRotation); 410 | } 411 | } 412 | }; 413 | animation.setRepeatCount(Animation.INFINITE); 414 | animation.setRepeatMode(Animation.RESTART); 415 | animation.setInterpolator(LINEAR_INTERPOLATOR); 416 | animation.setAnimationListener(new Animation.AnimationListener() { 417 | 418 | @Override 419 | public void onAnimationStart(Animation animation) { 420 | mRotationCount = 0; 421 | } 422 | 423 | @Override 424 | public void onAnimationEnd(Animation animation) { 425 | // do nothing 426 | } 427 | 428 | @Override 429 | public void onAnimationRepeat(Animation animation) { 430 | ring.storeOriginals(); 431 | ring.goToNextColor(); 432 | ring.setStartTrim(ring.getEndTrim()); 433 | if (mFinishing) { 434 | // finished closing the last ring from the swipe gesture; go 435 | // into progress mode 436 | mFinishing = false; 437 | animation.setDuration(ANIMATION_DURATION); 438 | ring.setShowArrow(false); 439 | } else { 440 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 441 | } 442 | } 443 | }); 444 | mAnimation = animation; 445 | } 446 | 447 | private final Callback mCallback = new Callback() { 448 | @Override 449 | public void invalidateDrawable(Drawable d) { 450 | invalidateSelf(); 451 | } 452 | 453 | @Override 454 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 455 | scheduleSelf(what, when); 456 | } 457 | 458 | @Override 459 | public void unscheduleDrawable(Drawable d, Runnable what) { 460 | unscheduleSelf(what); 461 | } 462 | }; 463 | 464 | private static class Ring { 465 | private final RectF mTempBounds = new RectF(); 466 | private final Paint mPaint = new Paint(); 467 | private final Paint mArrowPaint = new Paint(); 468 | 469 | private final Callback mCallback; 470 | 471 | private float mStartTrim = 0.0f; 472 | private float mEndTrim = 0.0f; 473 | private float mRotation = 0.0f; 474 | private float mStrokeWidth = 5.0f; 475 | private float mStrokeInset = 2.5f; 476 | 477 | private int[] mColors; 478 | // mColorIndex represents the offset into the available mColors that the 479 | // progress circle should currently display. As the progress circle is 480 | // animating, the mColorIndex moves by one to the next available color. 481 | private int mColorIndex; 482 | private float mStartingStartTrim; 483 | private float mStartingEndTrim; 484 | private float mStartingRotation; 485 | private boolean mShowArrow; 486 | private Path mArrow; 487 | private float mArrowScale; 488 | private double mRingCenterRadius; 489 | private int mArrowWidth; 490 | private int mArrowHeight; 491 | private int mAlpha; 492 | private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 493 | private int mBackgroundColor; 494 | private int mCurrentColor; 495 | 496 | public Ring(Callback callback) { 497 | mCallback = callback; 498 | 499 | mPaint.setStrokeCap(Paint.Cap.SQUARE); 500 | mPaint.setAntiAlias(true); 501 | mPaint.setStyle(Style.STROKE); 502 | 503 | mArrowPaint.setStyle(Style.FILL); 504 | mArrowPaint.setAntiAlias(true); 505 | } 506 | 507 | public void setBackgroundColor(int color) { 508 | mBackgroundColor = color; 509 | } 510 | 511 | /** 512 | * Set the dimensions of the arrowhead. 513 | * 514 | * @param width Width of the hypotenuse of the arrow head 515 | * @param height Height of the arrow point 516 | */ 517 | public void setArrowDimensions(float width, float height) { 518 | mArrowWidth = (int) width; 519 | mArrowHeight = (int) height; 520 | } 521 | 522 | /** 523 | * Draw the progress spinner 524 | */ 525 | public void draw(Canvas c, Rect bounds) { 526 | final RectF arcBounds = mTempBounds; 527 | arcBounds.set(bounds); 528 | arcBounds.inset(mStrokeInset, mStrokeInset); 529 | 530 | final float startAngle = (mStartTrim + mRotation) * 360; 531 | final float endAngle = (mEndTrim + mRotation) * 360; 532 | float sweepAngle = endAngle - startAngle; 533 | 534 | mPaint.setColor(mCurrentColor); 535 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); 536 | 537 | drawTriangle(c, startAngle, sweepAngle, bounds); 538 | 539 | if (mAlpha < 255) { 540 | mCirclePaint.setColor(mBackgroundColor); 541 | mCirclePaint.setAlpha(255 - mAlpha); 542 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, 543 | mCirclePaint); 544 | } 545 | } 546 | 547 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 548 | if (mShowArrow) { 549 | if (mArrow == null) { 550 | mArrow = new Path(); 551 | mArrow.setFillType(Path.FillType.EVEN_ODD); 552 | } else { 553 | mArrow.reset(); 554 | } 555 | 556 | // Adjust the position of the triangle so that it is inset as 557 | // much as the arc, but also centered on the arc. 558 | float inset = (int) mStrokeInset / 2 * mArrowScale; 559 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 560 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 561 | 562 | // Update the path each time. This works around an issue in SKIA 563 | // where concatenating a rotation matrix to a scale matrix 564 | // ignored a starting negative rotation. This appears to have 565 | // been fixed as of API 21. 566 | mArrow.moveTo(0, 0); 567 | mArrow.lineTo(mArrowWidth * mArrowScale, 0); 568 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 569 | * mArrowScale)); 570 | mArrow.offset(x - inset, y); 571 | mArrow.close(); 572 | // draw a triangle 573 | mArrowPaint.setColor(mCurrentColor); 574 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 575 | bounds.exactCenterY()); 576 | c.drawPath(mArrow, mArrowPaint); 577 | } 578 | } 579 | 580 | /** 581 | * Set the colors the progress spinner alternates between. 582 | * 583 | * @param colors Array of integers describing the colors. Must be non-null. 584 | */ 585 | public void setColors(@NonNull int[] colors) { 586 | mColors = colors; 587 | // if colors are reset, make sure to reset the color index as well 588 | setColorIndex(0); 589 | } 590 | 591 | /** 592 | * Set the absolute color of the progress spinner. This is should only 593 | * be used when animating between current and next color when the 594 | * spinner is rotating. 595 | * 596 | * @param color int describing the color. 597 | */ 598 | public void setColor(int color) { 599 | mCurrentColor = color; 600 | } 601 | 602 | /** 603 | * @param index Index into the color array of the color to display in 604 | * the progress spinner. 605 | */ 606 | public void setColorIndex(int index) { 607 | mColorIndex = index; 608 | mCurrentColor = mColors[mColorIndex]; 609 | } 610 | 611 | /** 612 | * @return int describing the next color the progress spinner should use when drawing. 613 | */ 614 | public int getNextColor() { 615 | return mColors[getNextColorIndex()]; 616 | } 617 | 618 | private int getNextColorIndex() { 619 | return (mColorIndex + 1) % (mColors.length); 620 | } 621 | 622 | /** 623 | * Proceed to the next available ring color. This will automatically 624 | * wrap back to the beginning of colors. 625 | */ 626 | public void goToNextColor() { 627 | setColorIndex(getNextColorIndex()); 628 | } 629 | 630 | public void setColorFilter(ColorFilter filter) { 631 | mPaint.setColorFilter(filter); 632 | invalidateSelf(); 633 | } 634 | 635 | /** 636 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 637 | */ 638 | public void setAlpha(int alpha) { 639 | mAlpha = alpha; 640 | } 641 | 642 | /** 643 | * @return Current alpha of the progress spinner and arrowhead. 644 | */ 645 | public int getAlpha() { 646 | return mAlpha; 647 | } 648 | 649 | /** 650 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 651 | */ 652 | public void setStrokeWidth(float strokeWidth) { 653 | mStrokeWidth = strokeWidth; 654 | mPaint.setStrokeWidth(strokeWidth); 655 | invalidateSelf(); 656 | } 657 | 658 | @SuppressWarnings("unused") 659 | public float getStrokeWidth() { 660 | return mStrokeWidth; 661 | } 662 | 663 | @SuppressWarnings("unused") 664 | public void setStartTrim(float startTrim) { 665 | mStartTrim = startTrim; 666 | invalidateSelf(); 667 | } 668 | 669 | @SuppressWarnings("unused") 670 | public float getStartTrim() { 671 | return mStartTrim; 672 | } 673 | 674 | public float getStartingStartTrim() { 675 | return mStartingStartTrim; 676 | } 677 | 678 | public float getStartingEndTrim() { 679 | return mStartingEndTrim; 680 | } 681 | 682 | public int getStartingColor() { 683 | return mColors[mColorIndex]; 684 | } 685 | 686 | @SuppressWarnings("unused") 687 | public void setEndTrim(float endTrim) { 688 | mEndTrim = endTrim; 689 | invalidateSelf(); 690 | } 691 | 692 | @SuppressWarnings("unused") 693 | public float getEndTrim() { 694 | return mEndTrim; 695 | } 696 | 697 | @SuppressWarnings("unused") 698 | public void setRotation(float rotation) { 699 | mRotation = rotation; 700 | invalidateSelf(); 701 | } 702 | 703 | @SuppressWarnings("unused") 704 | public float getRotation() { 705 | return mRotation; 706 | } 707 | 708 | public void setInsets(int width, int height) { 709 | final float minEdge = (float) Math.min(width, height); 710 | float insets; 711 | if (mRingCenterRadius <= 0 || minEdge < 0) { 712 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 713 | } else { 714 | insets = (float) (minEdge / 2.0f - mRingCenterRadius); 715 | } 716 | mStrokeInset = insets; 717 | } 718 | 719 | @SuppressWarnings("unused") 720 | public float getInsets() { 721 | return mStrokeInset; 722 | } 723 | 724 | /** 725 | * @param centerRadius Inner radius in px of the circle the progress 726 | * spinner arc traces. 727 | */ 728 | public void setCenterRadius(double centerRadius) { 729 | mRingCenterRadius = centerRadius; 730 | } 731 | 732 | public double getCenterRadius() { 733 | return mRingCenterRadius; 734 | } 735 | 736 | /** 737 | * @param show Set to true to show the arrow head on the progress spinner. 738 | */ 739 | public void setShowArrow(boolean show) { 740 | if (mShowArrow != show) { 741 | mShowArrow = show; 742 | invalidateSelf(); 743 | } 744 | } 745 | 746 | /** 747 | * @param scale Set the scale of the arrowhead for the spinner. 748 | */ 749 | public void setArrowScale(float scale) { 750 | if (scale != mArrowScale) { 751 | mArrowScale = scale; 752 | invalidateSelf(); 753 | } 754 | } 755 | 756 | /** 757 | * @return The amount the progress spinner is currently rotated, between [0..1]. 758 | */ 759 | public float getStartingRotation() { 760 | return mStartingRotation; 761 | } 762 | 763 | /** 764 | * If the start / end trim are offset to begin with, store them so that 765 | * animation starts from that offset. 766 | */ 767 | public void storeOriginals() { 768 | mStartingStartTrim = mStartTrim; 769 | mStartingEndTrim = mEndTrim; 770 | mStartingRotation = mRotation; 771 | } 772 | 773 | /** 774 | * Reset the progress spinner to default rotation, start and end angles. 775 | */ 776 | public void resetOriginals() { 777 | mStartingStartTrim = 0; 778 | mStartingEndTrim = 0; 779 | mStartingRotation = 0; 780 | setStartTrim(0); 781 | setEndTrim(0); 782 | setRotation(0); 783 | } 784 | 785 | private void invalidateSelf() { 786 | mCallback.invalidateDrawable(null); 787 | } 788 | } 789 | } 790 | -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/ic_loading_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/ic_loading_1.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/ic_loading_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/ic_loading_2.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/ic_loading_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/ic_loading_3.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/loading.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/loading_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/loading_1.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/loading_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/loading_2.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/loading_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/loading_3.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/loading_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/loading_4.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/loading_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/loading_5.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/loading_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/loading_6.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/loading_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/loading_7.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable-xxhdpi/loading_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxjava/HorizontalRefreshLayout/903c9a547c9fc0af3fa7a645ac69deee974f0999/lib/src/main/res/drawable-xxhdpi/loading_8.png -------------------------------------------------------------------------------- /lib/src/main/res/drawable/animated_rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/src/main/res/drawable/animated_rotate_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/src/main/res/drawable/spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/src/main/res/layout/common_loading_refresh_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /lib/src/main/res/layout/material_refresh_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /lib/src/main/res/layout/nice_refresh_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | lib 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':lib' 2 | --------------------------------------------------------------------------------