├── 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 | 
49 | 
50 | 
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 |
--------------------------------------------------------------------------------