Repository: XiaoQiWen/KRefreshLayout
Branch: master
Commit: 510cd29710d1
Files: 103
Total size: 326.7 KB
Directory structure:
gitextract_ue_irtns/
├── .gitattributes
├── .gitignore
├── LICENSE
├── LoadMore.md
├── README.md
├── app_java/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── gorden/
│ │ └── jrefresh/
│ │ └── demo/
│ │ ├── MainActivity.java
│ │ ├── header/
│ │ │ └── ClassicalHeader.java
│ │ └── util/
│ │ └── DensityUtil.java
│ └── res/
│ ├── layout/
│ │ ├── activity_main.xml
│ │ └── item_sample.xml
│ └── values/
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── app_kotlin/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── kotlin/
│ │ └── gorden/
│ │ └── krefreshlayout/
│ │ └── demo/
│ │ ├── App.java
│ │ ├── footer/
│ │ │ └── ClassicalFooter.java
│ │ ├── header/
│ │ │ ├── ClassicalHeader.java
│ │ │ ├── WechatHeader.java
│ │ │ ├── circle/
│ │ │ │ ├── AnimationView.java
│ │ │ │ └── CircleHeader.java
│ │ │ ├── fungame/
│ │ │ │ ├── BattleCityView.java
│ │ │ │ ├── FunGameFactory.java
│ │ │ │ ├── FunGameHeader.java
│ │ │ │ ├── FunGameView.java
│ │ │ │ └── HitBlockView.java
│ │ │ ├── materia/
│ │ │ │ ├── CircleImageView.java
│ │ │ │ ├── MateriaProgressHeader.java
│ │ │ │ └── MaterialProgressDrawable.java
│ │ │ ├── rentals/
│ │ │ │ ├── RentalsSunDrawable.java
│ │ │ │ └── RentalsSunHeaderView.java
│ │ │ └── storehouse/
│ │ │ ├── StoreHouseBarItem.java
│ │ │ ├── StoreHouseHeader.java
│ │ │ └── StoreHousePath.java
│ │ ├── ui/
│ │ │ ├── MainActivity.kt
│ │ │ ├── SampleActivity.kt
│ │ │ ├── SettingActivity.kt
│ │ │ └── fragment/
│ │ │ ├── ISampleFragment.kt
│ │ │ ├── SampleAFragment.kt
│ │ │ ├── SampleBFragment.kt
│ │ │ ├── SampleCFragment.kt
│ │ │ ├── SampleDFragment.kt
│ │ │ ├── SampleEFragment.kt
│ │ │ ├── SampleFFragment.kt
│ │ │ ├── SampleGFragment.kt
│ │ │ ├── SampleHFragment.kt
│ │ │ ├── SampleIFragment.kt
│ │ │ └── SampleJFragment.kt
│ │ ├── util/
│ │ │ ├── CrashHandler.java
│ │ │ ├── DensityUtil.java
│ │ │ └── XLog.java
│ │ └── widget/
│ │ ├── AdViewPager.java
│ │ ├── HeaderFloatBehavior.java
│ │ ├── HeaderScrollingBehavior.java
│ │ └── recyclerview/
│ │ ├── KLoadMoreView.kt
│ │ └── KRecyclerView.kt
│ └── res/
│ ├── drawable/
│ │ ├── bg_main.xml
│ │ └── selector_adpager_point.xml
│ ├── layout/
│ │ ├── activity_header_list.xml
│ │ ├── activity_main.xml
│ │ ├── activity_sample.xml
│ │ ├── activity_setting.xml
│ │ ├── item_main.xml
│ │ ├── item_sample.xml
│ │ ├── layout_coordinatorlayout.xml
│ │ ├── layout_krecyclerview.xml
│ │ ├── layout_nestedscrollview.xml
│ │ ├── layout_recyclerview.xml
│ │ ├── layout_scrollview.xml
│ │ ├── layout_viewpager.xml
│ │ ├── layout_vp_nestedscrollview.xml
│ │ ├── layout_vp_scrollview.xml
│ │ └── layout_webview.xml
│ └── values/
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── bintrayUpload.gradle
├── build.gradle
├── refresh-java/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── project.properties
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── gorden/
│ │ └── refresh/
│ │ ├── JRefreshHeader.java
│ │ └── JRefreshLayout.java
│ └── res/
│ └── values/
│ └── jattr.xml
├── refresh-kotlin/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── project.properties
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── kotlin/
│ │ └── gorden/
│ │ └── refresh/
│ │ ├── KRefreshHeader.kt
│ │ └── KRefreshLayout.kt
│ └── res/
│ └── values/
│ └── kattr.xml
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
*.java linguist-language=kotlin
================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.ap_
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
gradle/
build/
gradle.properties
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# Intellij
*.idea/
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/dictionaries
.idea/libraries
.idea/copyright
.idea/vcs.xml
# Keystore files
*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
gradlew
gradlew.bat
*.iml
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: LoadMore.md
================================================
#### 为什么不将LoadMore功能集成进RefreshLayout
* 实现效果不友好,loadMoreView加载完成后,会有个回弹效果(个人不喜欢)
* 加载更多的触发时机不好控制(比如滚动到内容倒数几条的时候自动加载)
---
[可以参考Demo基于RecyclerView实现的加载更多](https://github.com/XiaoQiWen/KRefreshLayout/tree/master/app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/widget/recyclerview) [LoadMoreFragment](https://github.com/XiaoQiWen/KRefreshLayout/blob/master/app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleJFragment.kt)

Demo中[KLoadMoreView](https://github.com/XiaoQiWen/KRefreshLayout/blob/master/app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/widget/recyclerview/KLoadMoreView.kt)说明
```
interface KLoadMoreView {
/**
* 自定义适当的加载时机
* @return true 自定义生效 false默认的加载时机
*/
fun shouldLoadMore(recyclerView: KRecyclerView):Boolean
/**
* 正在加载
*/
fun onLoadMore(recyclerView: KRecyclerView)
/**
* 加载完成
* @param hasMore 是否还有更多数据
*/
fun onComplete(recyclerView: KRecyclerView, hasMore:Boolean)
/**
* 加载失败
* @param errorCode 错误码,由用户定义
*/
fun onError(recyclerView: KRecyclerView, errorCode:Int)
}
```
[LoadMoreDemo.APK下载](https://github.com/XiaoQiWen/Resources/raw/master/KRefreshLayout/demo_new.apk)
================================================
FILE: README.md
================================================
# KRefreshLayout (JRefreshLayout)
kotlin和java两个版本的下拉刷新框架,支持任意View、支持定制任意header
## Download[](https://github.com/XiaoQiWen/KRefreshLayout/releases)
#### KRefreshLayout
gradle
```
compile 'gorden.refresh:refresh-kotlin:1.3'
```
maven
```
gorden.refresh
refresh-kotlin
1.3
pom
```
``注意:kotlin版本目前需要下载插件或者使用AndroidStudio3.0+``
#### JRefreshLayout
gradle
```
compile 'gorden.refresh:refresh-java:1.3'
```
maven
```
gorden.refresh
refresh-java
1.3
pom






## Usage
``KRefreshLayou详细使用说明:``
>* [RefreshHeader接口说明](https://github.com/XiaoQiWen/KRefreshLayout/wiki/RefreshHeader%E6%96%B9%E6%B3%95%E8%AF%B4%E6%98%8E)
>* [RefreshLayout开放Api](https://github.com/XiaoQiWen/KRefreshLayout/wiki/RefreshLayout%E5%BC%80%E6%94%BEApi)
>* [XML参数配置](https://github.com/XiaoQiWen/KRefreshLayout/wiki/RefreshHeader-XML%E5%8F%AF%E9%85%8D%E7%BD%AE%E5%8F%82%E6%95%B0)
>* [IOS边缘滚动效果实现](https://github.com/XiaoQiWen/KRefreshLayout/wiki/%E4%BB%BFIos%E8%BE%B9%E7%BC%98%E6%BB%9A%E5%8A%A8%E6%95%88%E6%9E%9C)
>* [微信刷新Header实现](https://github.com/XiaoQiWen/KRefreshLayout/wiki/%E5%BE%AE%E4%BF%A1%E5%88%B7%E6%96%B0Header%E5%AE%9E%E7%8E%B0)
设置刷新监听
```
refreshLayout.setKRefreshListener {
refreshLayout.postDelayed({
//这里的true是指刷新成功,在header接口中complete能接收到这参数
refreshLayout.refreshComplete(true)
}, 2000)
}
```
---
[关于加载更多](https://github.com/XiaoQiWen/KRefreshLayout/blob/master/LoadMore.md)
更多请参考Demo
### 联系方式
* QQ: 354419188
* Email: gordenxqw@gmail.com
### License
Copyright (C) 2017 XiaoQiWen
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: app_java/.gitignore
================================================
/build
================================================
FILE: app_java/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
buildToolsVersion "27.0.1"
defaultConfig {
applicationId "gorden.jrefresh.demo"
minSdkVersion 15
versionCode 1
versionName "1.1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:design:27.0.0'
compile 'com.android.support:appcompat-v7:27.0.0'
compile project(':refresh-java')
}
================================================
FILE: app_java/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in F:\AndroidSdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app_java/src/main/AndroidManifest.xml
================================================
================================================
FILE: app_java/src/main/java/gorden/jrefresh/demo/MainActivity.java
================================================
package gorden.jrefresh.demo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import gorden.refresh.JRefreshLayout;
public class MainActivity extends AppCompatActivity {
JRefreshLayout refreshLayout;
RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
refreshLayout = (JRefreshLayout) findViewById(R.id.refreshLayout);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setAdapter(new RecyclerView.Adapter() {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new RecyclerView.ViewHolder(LayoutInflater.from(getBaseContext()).inflate(R.layout.item_sample,null)) {
};
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 20;
}
});
refreshLayout.setJRefreshListener(new JRefreshLayout.JRefreshListener() {
@Override
public void onRefresh(final JRefreshLayout refreshLayout) {
refreshLayout.postDelayed(new Runnable() {
@Override
public void run() {
refreshLayout.refreshComplete(true);
}
},3000);
}
});
}
}
================================================
FILE: app_java/src/main/java/gorden/jrefresh/demo/header/ClassicalHeader.java
================================================
package gorden.jrefresh.demo.header;
import android.content.Context;
import android.graphics.Color;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import gorden.jrefresh.demo.R;
import gorden.jrefresh.demo.util.DensityUtil;
import gorden.refresh.JRefreshHeader;
import gorden.refresh.JRefreshLayout;
/**
* 经典下拉刷新
* Created by Gorden on 2017/6/17.
*/
public class ClassicalHeader extends FrameLayout implements JRefreshHeader {
private static final String TAG = "ClassicalHeader";
private ImageView arrawImg;
private TextView textTitle;
private RotateAnimation rotateAnimation = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
public ClassicalHeader(@NonNull Context context) {
this(context,null);
}
public ClassicalHeader(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public ClassicalHeader(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
LinearLayout root = new LinearLayout(context);
root.setOrientation(LinearLayout.HORIZONTAL);
root.setGravity(Gravity.CENTER_VERTICAL);
addView(root,LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
((LayoutParams)root.getLayoutParams()).gravity = Gravity.CENTER;
arrawImg = new ImageView(context);
arrawImg.setImageResource(R.drawable.ic_arrow_down);
arrawImg.setScaleType(ImageView.ScaleType.CENTER);
root.addView(arrawImg);
textTitle = new TextView(context);
textTitle.setTextSize(13);
textTitle.setText("下拉刷新...");
textTitle.setTextColor(Color.parseColor("#999999"));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
params.leftMargin = 20;
root.addView(textTitle,params);
rotateAnimation.setDuration(800);
rotateAnimation.setInterpolator(new LinearInterpolator());
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setRepeatMode(Animation.RESTART);
setPadding(0, DensityUtil.dip2px(15),0,DensityUtil.dip2px(15));
}
@Override
public long succeedRetention() {
return 200;
}
@Override
public long failingRetention() {
return 0;
}
@Override
public int refreshHeight() {
return getHeight();
}
@Override
public int maxOffsetHeight() {
return 4*getHeight();
}
boolean isReset = true;
@Override
public void onReset( JRefreshLayout refreshLayout) {
Log.e(TAG,"----------------> onReset");
arrawImg.setImageResource(R.drawable.ic_arrow_down);
textTitle.setText("下拉刷新...");
isReset = true;
arrawImg.setVisibility(VISIBLE);
}
@Override
public void onPrepare( JRefreshLayout refreshLayout) {
Log.e(TAG,"----------------> onPrepare");
arrawImg.setImageResource(R.drawable.ic_arrow_down);
textTitle.setText("下拉刷新...");
}
@Override
public void onRefresh( JRefreshLayout refreshLayout) {
Log.e(TAG,"----------------> onRefresh");
arrawImg.setImageResource(R.drawable.ic_loading);
arrawImg.startAnimation(rotateAnimation);
textTitle.setText("加载中...");
isReset = false;
}
@Override
public void onComplete( JRefreshLayout refreshLayout,boolean isSuccess) {
Log.e(TAG,"----------------> onComplete");
arrawImg.clearAnimation();
arrawImg.setVisibility(GONE);
if (isSuccess){
textTitle.setText("刷新完成...");
}else{
textTitle.setText("刷新失败...");
}
}
boolean attain = false;
@Override
public void onScroll( JRefreshLayout refreshLayout, int distance, float percent,boolean refreshing) {
Log.e(TAG,"----------------> onScroll "+percent);
if (!refreshing&&isReset){
if(percent>=1&&!attain){
attain = true;
textTitle.setText("释放刷新...");
arrawImg.animate().rotation(-180).start();
}else if (percent<1&&attain){
attain = false;
arrawImg.animate().rotation(0).start();
textTitle.setText("下拉刷新...");
}
}
}
}
================================================
FILE: app_java/src/main/java/gorden/jrefresh/demo/util/DensityUtil.java
================================================
package gorden.jrefresh.demo.util;
import android.content.res.Resources;
/**
* document
* Created by Gordn on 2017/6/19.
*/
public class DensityUtil {
private static float density = Resources.getSystem().getDisplayMetrics().density;
public static int dip2px(int dp){
return (int) (dp*density);
}
public static int dip2px(float dp){
return (int) (dp*density);
}
public static int appWidth(){
return Resources.getSystem().getDisplayMetrics().widthPixels;
}
}
================================================
FILE: app_java/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: app_java/src/main/res/layout/item_sample.xml
================================================
================================================
FILE: app_java/src/main/res/values/colors.xml
================================================
#3F51B5
#303F9F
#FF4081
================================================
FILE: app_java/src/main/res/values/strings.xml
================================================
JRefreshLayout
================================================
FILE: app_java/src/main/res/values/styles.xml
================================================
================================================
FILE: app_kotlin/.gitignore
================================================
/build
================================================
FILE: app_kotlin/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
buildToolsVersion "27.0.1"
defaultConfig {
applicationId "gorden.krefreshlayout.demo"
minSdkVersion 15
versionCode 1
versionName "1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
// compile 'gorden.refresh:refresh-kotlin:1.0'
compile project(path: ':refresh-kotlin')
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile 'com.android.support:appcompat-v7:27.0.0'
compile 'com.android.support:design:27.0.0'
testCompile 'junit:junit:4.12'
}
repositories {
mavenCentral()
}
================================================
FILE: app_kotlin/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in D:\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app_kotlin/src/main/AndroidManifest.xml
================================================
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/App.java
================================================
package gorden.krefreshlayout.demo;
import android.app.Application;
import android.content.Intent;
import gorden.krefreshlayout.demo.ui.SampleActivity;
import gorden.krefreshlayout.demo.util.CrashHandler;
/**
* Created by Gorden on 2017/6/20.
*/
public class App extends Application{
@Override
public void onCreate() {
super.onCreate();
CrashHandler.getInstance().init(this,true);
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/footer/ClassicalFooter.java
================================================
package gorden.krefreshlayout.demo.footer;
import android.content.Context;
import android.graphics.Color;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.jetbrains.annotations.NotNull;
import gorden.krefreshlayout.demo.R;
import gorden.krefreshlayout.demo.util.DensityUtil;
import gorden.krefreshlayout.demo.widget.recyclerview.KLoadMoreView;
import gorden.krefreshlayout.demo.widget.recyclerview.KRecyclerView;
/**
* 经典下拉刷新
* Created by Gorden on 2017/6/17.
*/
public class ClassicalFooter extends FrameLayout implements KLoadMoreView {
private KRecyclerView recyclerView;
private ImageView arrawImg;
private TextView textTitle;
private RotateAnimation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
public ClassicalFooter(@NonNull Context context) {
this(context, null);
}
public ClassicalFooter(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ClassicalFooter(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
LinearLayout root = new LinearLayout(context);
root.setOrientation(LinearLayout.HORIZONTAL);
root.setGravity(Gravity.CENTER_VERTICAL);
addView(root, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
((LayoutParams) root.getLayoutParams()).gravity = Gravity.CENTER;
arrawImg = new ImageView(context);
arrawImg.setImageResource(R.drawable.ic_loading);
arrawImg.setScaleType(ImageView.ScaleType.CENTER);
root.addView(arrawImg);
textTitle = new TextView(context);
textTitle.setTextSize(13);
textTitle.setText("上拉或点击加载更多...");
textTitle.setTextColor(Color.parseColor("#999999"));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.leftMargin = 20;
root.addView(textTitle, params);
rotateAnimation.setDuration(800);
rotateAnimation.setInterpolator(new LinearInterpolator());
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setRepeatMode(Animation.RESTART);
setPadding(0, DensityUtil.dip2px(15), 0, DensityUtil.dip2px(15));
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
recyclerView.startLoadMore();
}
});
}
@Override
public boolean shouldLoadMore(@NotNull KRecyclerView recyclerView) {
this.recyclerView = recyclerView;
return false;
}
@Override
public void onLoadMore(@NotNull KRecyclerView recyclerView) {
arrawImg.setVisibility(VISIBLE);
arrawImg.startAnimation(rotateAnimation);
textTitle.setText("正在加载...");
}
@Override
public void onComplete(@NotNull KRecyclerView recyclerView, boolean hasMore) {
arrawImg.clearAnimation();
textTitle.setText(hasMore ? "上拉或点击加载更多..." : "没有更多数据");
arrawImg.setVisibility(GONE);
}
@Override
public void onError(@NotNull KRecyclerView recyclerView, int errorCode) {
arrawImg.clearAnimation();
textTitle.setText("加载失败,点击重新加载");
arrawImg.setVisibility(GONE);
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/ClassicalHeader.java
================================================
package gorden.krefreshlayout.demo.header;
import android.content.Context;
import android.graphics.Color;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.jetbrains.annotations.NotNull;
import gorden.krefreshlayout.demo.R;
import gorden.krefreshlayout.demo.util.DensityUtil;
import gorden.refresh.KRefreshHeader;
import gorden.refresh.KRefreshLayout;
/**
* 经典下拉刷新
* Created by Gorden on 2017/6/17.
*/
public class ClassicalHeader extends FrameLayout implements KRefreshHeader {
private static final String TAG = "ClassicalHeader";
private ImageView arrawImg;
private TextView textTitle;
private RotateAnimation rotateAnimation = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
public ClassicalHeader(@NonNull Context context) {
this(context,null);
}
public ClassicalHeader(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public ClassicalHeader(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
LinearLayout root = new LinearLayout(context);
root.setOrientation(LinearLayout.HORIZONTAL);
root.setGravity(Gravity.CENTER_VERTICAL);
addView(root,LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
((LayoutParams)root.getLayoutParams()).gravity = Gravity.CENTER;
arrawImg = new ImageView(context);
arrawImg.setImageResource(R.drawable.ic_arrow_down);
arrawImg.setScaleType(ImageView.ScaleType.CENTER);
root.addView(arrawImg);
textTitle = new TextView(context);
textTitle.setTextSize(13);
textTitle.setText("下拉刷新...");
textTitle.setTextColor(Color.parseColor("#999999"));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
params.leftMargin = 20;
root.addView(textTitle,params);
rotateAnimation.setDuration(800);
rotateAnimation.setInterpolator(new LinearInterpolator());
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setRepeatMode(Animation.RESTART);
setPadding(0, DensityUtil.dip2px(15),0,DensityUtil.dip2px(15));
}
@Override
public long succeedRetention() {
return 200;
}
@Override
public long failingRetention() {
return 0;
}
@Override
public int refreshHeight() {
return getHeight();
}
@Override
public int maxOffsetHeight() {
return 4*getHeight();
}
boolean isReset = true;
@Override
public void onReset(@NotNull KRefreshLayout refreshLayout) {
Log.e(TAG,"----------------> onReset");
arrawImg.setImageResource(R.drawable.ic_arrow_down);
textTitle.setText("下拉刷新...");
isReset = true;
arrawImg.setVisibility(VISIBLE);
}
@Override
public void onPrepare(@NotNull KRefreshLayout refreshLayout) {
Log.e(TAG,"----------------> onPrepare");
arrawImg.setImageResource(R.drawable.ic_arrow_down);
textTitle.setText("下拉刷新...");
}
@Override
public void onRefresh(@NotNull KRefreshLayout refreshLayout) {
Log.e(TAG,"----------------> onRefresh");
arrawImg.setImageResource(R.drawable.ic_loading);
arrawImg.startAnimation(rotateAnimation);
textTitle.setText("加载中...");
isReset = false;
}
@Override
public void onComplete(@NotNull KRefreshLayout refreshLayout,boolean isSuccess) {
Log.e(TAG,"----------------> onComplete");
arrawImg.clearAnimation();
arrawImg.setVisibility(GONE);
if (isSuccess){
textTitle.setText("刷新完成...");
}else{
textTitle.setText("刷新失败...");
}
}
boolean attain = false;
@Override
public void onScroll(@NotNull KRefreshLayout refreshLayout, int distance, float percent,boolean refreshing) {
Log.e(TAG,"----------------> onScroll "+percent);
if (!refreshing&&isReset){
if(percent>=1&&!attain){
attain = true;
textTitle.setText("释放刷新...");
arrawImg.animate().rotation(-180).start();
}else if (percent<1&&attain){
attain = false;
arrawImg.animate().rotation(0).start();
textTitle.setText("下拉刷新...");
}
}
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/WechatHeader.java
================================================
package gorden.krefreshlayout.demo.header;
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import org.jetbrains.annotations.NotNull;
import gorden.krefreshlayout.demo.R;
import gorden.krefreshlayout.demo.util.DensityUtil;
import gorden.refresh.KRefreshHeader;
import gorden.refresh.KRefreshLayout;
/**
* document
* Created by Gordn on 2017/6/26.
*/
public class WechatHeader extends FrameLayout implements KRefreshHeader {
private ImageView imgChat;
private RotateAnimation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
private ValueAnimator returnAnima = new ValueAnimator();
public WechatHeader(@NonNull Context context) {
this(context, null);
}
public WechatHeader(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
imgChat = new ImageView(context);
imgChat.setImageResource(R.drawable.ic_wechat);
LayoutParams params = new LayoutParams(DensityUtil.dip2px(30), DensityUtil.dip2px(30));
params.leftMargin = DensityUtil.dip2px(20);
addView(imgChat, params);
rotateAnimation.setDuration(800);
rotateAnimation.setInterpolator(new LinearInterpolator());
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setRepeatMode(Animation.RESTART);
returnAnima.setDuration(800);
returnAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int progress = (int) animation.getAnimatedValue();
offsetTopAndBottom(progress - mDistance);
imgChat.setRotation(progress);
mDistance = progress;
if (getParent() instanceof KRefreshLayout) {
((KRefreshLayout) getParent()).setHeaderOffset(mDistance - lastDistance);
}
}
});
}
@Override
public long succeedRetention() {
return 0;
}
@Override
public long failingRetention() {
return 0;
}
@Override
public int refreshHeight() {
return DensityUtil.dip2px(50);
}
@Override
public int maxOffsetHeight() {
return ((View) getParent()).getHeight();
}
@Override
public void onReset(@NotNull KRefreshLayout refreshLayout) {
}
@Override
public void onPrepare(@NotNull KRefreshLayout refreshLayout) {
}
@Override
public void onRefresh(@NotNull KRefreshLayout refreshLayout) {
imgChat.startAnimation(rotateAnimation);
}
@Override
public void onComplete(@NotNull KRefreshLayout refreshLayout, boolean isSuccess) {
imgChat.clearAnimation();
returnAnima.setIntValues(mDistance, 0);
returnAnima.start();
}
public int mDistance = 0;
private int lastDistance;
@Override
public void onScroll(@NotNull KRefreshLayout refreshLayout, int distance, float percent, boolean refreshing) {
int offset = distance - lastDistance;
if (returnAnima.isRunning())
returnAnima.cancel();
lastDistance = distance;
if (!refreshing) {
imgChat.setRotation(-distance);
if (percent > 1) {
offsetTopAndBottom(-offset);
if (mDistance != refreshHeight()) {
offset = refreshHeight() - mDistance;
offsetTopAndBottom(offset);
mDistance += offset;
}
} else {
if (mDistance + offset != distance) {
offset = distance - (mDistance + offset);
offsetTopAndBottom(offset);
}
mDistance = distance;
}
} else {
offsetTopAndBottom(-offset);
}
refreshLayout.setHeaderOffset(mDistance - distance);
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/circle/AnimationView.java
================================================
package gorden.krefreshlayout.demo.header.circle;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by zhanglei on 15/7/18.
*/
public class AnimationView extends View {
private static final String TAG = "AnimationView";
private int PULL_HEIGHT;
private int PULL_DELTA;
private float mWidthOffset;
private AnimatorStatus mAniStatus = AnimatorStatus.PULL_DOWN;
enum AnimatorStatus {
PULL_DOWN,
DRAG_DOWN,
REL_DRAG,
SPRING_UP, // rebound to up, the position is less than PULL_HEIGHT
POP_BALL,
OUTER_CIR,
REFRESHING,
DONE,
STOP;
@Override
public String toString() {
switch (this) {
case PULL_DOWN:
return "pull down";
case DRAG_DOWN:
return "drag down";
case REL_DRAG:
return "release drag";
case SPRING_UP:
return "spring up";
case POP_BALL:
return "pop ball";
case OUTER_CIR:
return "outer circle";
case REFRESHING:
return "refreshing...";
case DONE:
return "done!";
case STOP:
return "stop";
default:
return "unknown state";
}
}
}
private Paint mBackPaint;
private Paint mBallPaint;
private Paint mOutPaint;
private Path mPath;
public AnimationView(Context context) {
this(context, null, 0);
}
public AnimationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs, defStyleAttr);
}
private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
PULL_HEIGHT = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics());
PULL_DELTA = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics());
mWidthOffset = 0.5f;
mBackPaint = new Paint();
mBackPaint.setAntiAlias(true);
mBackPaint.setStyle(Paint.Style.FILL);
mBackPaint.setColor(0xff8b90af);
mBallPaint = new Paint();
mBallPaint.setAntiAlias(true);
mBallPaint.setColor(0xffffffff);
mBallPaint.setStyle(Paint.Style.FILL);
mOutPaint = new Paint();
mOutPaint.setAntiAlias(true);
mOutPaint.setColor(0xffffffff);
mOutPaint.setStyle(Paint.Style.STROKE);
mOutPaint.setStrokeWidth(5);
mPath = new Path();
}
private int mRadius;
private int mWidth;
private int mHeight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
if (height > PULL_DELTA + PULL_HEIGHT) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(PULL_DELTA + PULL_HEIGHT, MeasureSpec.getMode(heightMeasureSpec));
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mRadius = getHeight() / 6;
mWidth = getWidth();
mHeight = getHeight();
if (mHeight < PULL_HEIGHT) {
mAniStatus = AnimatorStatus.PULL_DOWN;
}
switch (mAniStatus) {
case PULL_DOWN:
if (mHeight >= PULL_HEIGHT) {
mAniStatus = AnimatorStatus.DRAG_DOWN;
}
break;
case REL_DRAG:
break;
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
switch (mAniStatus) {
case PULL_DOWN:
canvas.drawRect(0, 0, mWidth, mHeight, mBackPaint);
break;
case REL_DRAG:
case DRAG_DOWN:
drawDrag(canvas);
break;
case SPRING_UP:
drawSpring(canvas, getSpringDelta());
invalidate();
break;
case POP_BALL:
drawPopBall(canvas);
invalidate();
break;
case OUTER_CIR:
drawOutCir(canvas);
invalidate();
break;
case REFRESHING:
drawRefreshing(canvas);
invalidate();
break;
case DONE:
drawDone(canvas);
invalidate();
break;
case STOP:
drawDone(canvas);
break;
}
if (mAniStatus == AnimatorStatus.REL_DRAG) {
ViewGroup.LayoutParams params = getLayoutParams();
int height;
// NOTICE: If the height equals mLastHeight, then the requestLayout() will not work correctly
do {
height = getRelHeight();
} while (height == mLastHeight && getRelRatio() != 1);
mLastHeight = height;
params.height = PULL_HEIGHT + height;
requestLayout();
}
}
private void drawDrag(Canvas canvas) {
canvas.drawRect(0, 0, mWidth, PULL_HEIGHT, mBackPaint);
mPath.reset();
mPath.moveTo(0, PULL_HEIGHT);
mPath.quadTo(mWidthOffset * mWidth, PULL_HEIGHT + (mHeight - PULL_HEIGHT) * 2,
mWidth, PULL_HEIGHT);
canvas.drawPath(mPath, mBackPaint);
}
private void drawSpring(Canvas canvas, int springDelta) {
mPath.reset();
mPath.moveTo(0, 0);
mPath.lineTo(0, PULL_HEIGHT);
mPath.quadTo(mWidth / 2, PULL_HEIGHT - springDelta,
mWidth, PULL_HEIGHT);
mPath.lineTo(mWidth, 0);
canvas.drawPath(mPath, mBackPaint);
int curH = PULL_HEIGHT - springDelta / 2;
if (curH > PULL_HEIGHT - PULL_DELTA / 2) {
int leftX = (int) (mWidth / 2 - 2 * mRadius + getSprRatio() * mRadius);
mPath.reset();
mPath.moveTo(leftX, curH);
mPath.quadTo(mWidth / 2, curH - mRadius * getSprRatio() * 2,
mWidth - leftX, curH);
canvas.drawPath(mPath, mBallPaint);
} else {
canvas.drawArc(new RectF(mWidth / 2 - mRadius, curH - mRadius, mWidth / 2 + mRadius, curH + mRadius),
180, 180, true, mBallPaint);
}
}
private void drawPopBall(Canvas canvas) {
mPath.reset();
mPath.moveTo(0, 0);
mPath.lineTo(0, PULL_HEIGHT);
mPath.quadTo(mWidth / 2, PULL_HEIGHT - PULL_DELTA,
mWidth, PULL_HEIGHT);
mPath.lineTo(mWidth, 0);
canvas.drawPath(mPath, mBackPaint);
int cirCentStart = PULL_HEIGHT - PULL_DELTA / 2;
int cirCenY = (int) (cirCentStart - mRadius * 2 * getPopRatio());
canvas.drawArc(new RectF(mWidth / 2 - mRadius, cirCenY - mRadius, mWidth / 2 + mRadius, cirCenY + mRadius),
180, 360, true, mBallPaint);
if (getPopRatio() < 1) {
drawTail(canvas, cirCenY, cirCentStart + 1, getPopRatio());
} else {
canvas.drawCircle(mWidth / 2, cirCenY, mRadius, mBallPaint);
}
}
private void drawTail(Canvas canvas, int centerY, int bottom, float fraction) {
int bezier1w = (int) (mWidth / 2 + (mRadius * 3 / 4) * (1 - fraction));
PointF start = new PointF(mWidth / 2 + mRadius, centerY);
PointF bezier1 = new PointF(bezier1w, bottom);
PointF bezier2 = new PointF(bezier1w + mRadius / 2, bottom);
mPath.reset();
mPath.moveTo(start.x, start.y);
mPath.quadTo(bezier1.x, bezier1.y,
bezier2.x, bezier2.y);
mPath.lineTo(mWidth - bezier2.x, bezier2.y);
mPath.quadTo(mWidth - bezier1.x, bezier1.y,
mWidth - start.x, start.y);
canvas.drawPath(mPath, mBallPaint);
}
private void drawOutCir(Canvas canvas) {
mPath.reset();
mPath.moveTo(0, 0);
mPath.lineTo(0, PULL_HEIGHT);
mPath.quadTo(mWidth / 2, PULL_HEIGHT - (1 - getOutRatio()) * PULL_DELTA,
mWidth, PULL_HEIGHT);
mPath.lineTo(mWidth, 0);
canvas.drawPath(mPath, mBackPaint);
int innerY = PULL_HEIGHT - PULL_DELTA / 2 - mRadius * 2;
canvas.drawCircle(mWidth / 2, innerY, mRadius, mBallPaint);
}
private int mRefreshStart = 90;
private int mRefreshStop = 90;
private int TARGET_DEGREE = 270;
private boolean mIsStart = true;
private boolean mIsRefreshing = true;
private void drawRefreshing(Canvas canvas) {
canvas.drawRect(0, 0, mWidth, mHeight, mBackPaint);
int innerY = PULL_HEIGHT - PULL_DELTA / 2 - mRadius * 2;
canvas.drawCircle(mWidth / 2, innerY, mRadius, mBallPaint);
int outerR = mRadius + 10;
mRefreshStart += mIsStart ? 3 : 10;
mRefreshStop += mIsStart ? 10 : 3;
mRefreshStart = mRefreshStart % 360;
mRefreshStop = mRefreshStop % 360;
int swipe = mRefreshStop - mRefreshStart;
swipe = swipe < 0 ? swipe + 360 : swipe;
canvas.drawArc(new RectF(mWidth / 2 - outerR, innerY - outerR, mWidth / 2 + outerR, innerY + outerR),
mRefreshStart, swipe, false, mOutPaint);
if (swipe >= TARGET_DEGREE) {
mIsStart = false;
} else if (swipe <= 10) {
mIsStart = true;
}
if (!mIsRefreshing) {
applyDone();
}
}
// stop refreshing
public void setRefreshing(boolean isFresh) {
mIsRefreshing = isFresh;
}
private void drawDone(Canvas canvas) {
int beforeColor = mOutPaint.getColor();
if (getDoneRatio() < 0.3) {
canvas.drawRect(0, 0, mWidth, mHeight, mBackPaint);
int innerY = PULL_HEIGHT - PULL_DELTA / 2 - mRadius * 2;
canvas.drawCircle(mWidth / 2, innerY, mRadius, mBallPaint);
int outerR = (int) (mRadius + 10 + 10 * getDoneRatio() / 0.3f);
int afterColor = Color.argb((int) (0xff * (1 - getDoneRatio() / 0.3f)), Color.red(beforeColor),
Color.green(beforeColor), Color.blue(beforeColor));
mOutPaint.setColor(afterColor);
canvas.drawArc(new RectF(mWidth / 2 - outerR, innerY - outerR, mWidth / 2 + outerR, innerY + outerR),
0, 360, false, mOutPaint);
}
mOutPaint.setColor(beforeColor);
if (getDoneRatio() >= 0.3 && getDoneRatio() < 0.7) {
canvas.drawRect(0, 0, mWidth, mHeight, mBackPaint);
float fraction = (getDoneRatio() - 0.3f) / 0.4f;
int startCentY = PULL_HEIGHT - PULL_DELTA / 2 - mRadius * 2;
int curCentY = (int) (startCentY + (PULL_DELTA / 2 + mRadius * 2) * fraction);
canvas.drawCircle(mWidth / 2, curCentY, mRadius, mBallPaint);
if (curCentY >= PULL_HEIGHT - mRadius * 2) {
drawTail(canvas, curCentY, PULL_HEIGHT, (1 - fraction));
}
}
if (getDoneRatio() >= 0.7 && getDoneRatio() <= 1) {
float fraction = (getDoneRatio() - 0.7f) / 0.3f;
canvas.drawRect(0, 0, mWidth, mHeight, mBackPaint);
int leftX = (int) (mWidth / 2 - mRadius - 2 * mRadius * fraction);
mPath.reset();
mPath.moveTo(leftX, PULL_HEIGHT);
mPath.quadTo(mWidth / 2, PULL_HEIGHT - (mRadius * (1 - fraction)),
mWidth - leftX, PULL_HEIGHT);
canvas.drawPath(mPath, mBallPaint);
}
}
private int mLastHeight;
private int getRelHeight() {
return (int) (mSpriDeta * (1 - getRelRatio()));
}
private int getSpringDelta() {
return (int) (PULL_DELTA * getSprRatio());
}
private static long REL_DRAG_DUR = 200;
private long mStart;
private long mStop;
private int mSpriDeta;
public void releaseDrag() {
mStart = System.currentTimeMillis();
mStop = mStart + REL_DRAG_DUR;
mAniStatus = AnimatorStatus.REL_DRAG;
mSpriDeta = mHeight - PULL_HEIGHT;
requestLayout();
}
private float getRelRatio() {
if (System.currentTimeMillis() >= mStop) {
springUp();
return 1;
}
float ratio = (System.currentTimeMillis() - mStart) / (float) REL_DRAG_DUR;
return Math.min(ratio, 1);
}
private static long SPRING_DUR = 200;
private long mSprStart;
private long mSprStop;
private void springUp() {
mSprStart = System.currentTimeMillis();
mSprStop = mSprStart + SPRING_DUR;
mAniStatus = AnimatorStatus.SPRING_UP;
invalidate();
}
private float getSprRatio() {
if (System.currentTimeMillis() >= mSprStop) {
popBall();
return 1;
}
float ratio = (System.currentTimeMillis() - mSprStart) / (float) SPRING_DUR;
return Math.min(1, ratio);
}
private static final long POP_BALL_DUR = 300;
private long mPopStart;
private long mPopStop;
private void popBall() {
mPopStart = System.currentTimeMillis();
mPopStop = mPopStart + POP_BALL_DUR;
mAniStatus = AnimatorStatus.POP_BALL;
invalidate();
}
private float getPopRatio() {
if (System.currentTimeMillis() >= mPopStop) {
startOutCir();
return 1;
}
float ratio = (System.currentTimeMillis() - mPopStart) / (float) POP_BALL_DUR;
return Math.min(ratio, 1);
}
private static final long OUTER_DUR = 200;
private long mOutStart;
private long mOutStop;
private void startOutCir() {
mOutStart = System.currentTimeMillis();
mOutStop = mOutStart + OUTER_DUR;
mAniStatus = AnimatorStatus.OUTER_CIR;
mRefreshStart = 90;
mRefreshStop = 90;
TARGET_DEGREE = 270;
mIsStart = true;
mIsRefreshing = true;
invalidate();
}
private float getOutRatio() {
if (System.currentTimeMillis() >= mOutStop) {
mAniStatus = AnimatorStatus.REFRESHING;
mIsRefreshing = true;
return 1;
}
float ratio = (System.currentTimeMillis() - mOutStart) / (float) OUTER_DUR;
return Math.min(ratio, 1);
}
private static final long DONE_DUR = 1000;
private long mDoneStart;
private long mDoneStop;
private void applyDone() {
mDoneStart = System.currentTimeMillis();
mDoneStop = mDoneStart + DONE_DUR;
mAniStatus = AnimatorStatus.DONE;
}
private float getDoneRatio() {
if (System.currentTimeMillis() >= mDoneStop) {
mAniStatus = AnimatorStatus.STOP;
if (onViewAniDone != null) {
onViewAniDone.viewAniDone();
}
return 1;
}
float ratio = (System.currentTimeMillis() - mDoneStart) / (float) DONE_DUR;
return Math.min(ratio, 1);
}
private OnViewAniDone onViewAniDone;
public void setOnViewAniDone(OnViewAniDone onViewAniDone) {
this.onViewAniDone = onViewAniDone;
}
interface OnViewAniDone {
void viewAniDone();
}
public void setAniBackColor(int color) {
mBackPaint.setColor(color);
}
public void setAniForeColor(int color) {
mBallPaint.setColor(color);
mOutPaint.setColor(color);
setBackgroundColor(color);
}
// the height of view is smallTimes times of circle radius
public void setRadius(int smallTimes) {
mRadius = mHeight / smallTimes;
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/circle/CircleHeader.java
================================================
package gorden.krefreshlayout.demo.header.circle;
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import org.jetbrains.annotations.NotNull;
import gorden.krefreshlayout.demo.util.DensityUtil;
import gorden.refresh.KRefreshHeader;
import gorden.refresh.KRefreshLayout;
/**
* document
* Created by Gordn on 2017/6/22.
*/
public class CircleHeader extends FrameLayout implements KRefreshHeader {
AnimationView mHeader;
private ValueAnimator mUpTopAnimator;
public CircleHeader(@NonNull Context context) {
super(context);
mHeader = new AnimationView(context);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
params.gravity = Gravity.TOP;
mHeader.setLayoutParams(params);
addView(mHeader);
mHeader.setAniBackColor(0xff8b90af);
mHeader.setAniForeColor(0xffffffff);
mHeader.setRadius(7);
mHeader.setOnViewAniDone(new AnimationView.OnViewAniDone() {
@Override
public void viewAniDone() {
mUpTopAnimator.start();
}
});
mUpTopAnimator = ValueAnimator.ofFloat(refreshHeight(), 0);
mUpTopAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float val = (float) animation.getAnimatedValue();
mHeader.getLayoutParams().height = (int) val;
mHeader.requestLayout();
}
});
mUpTopAnimator.setDuration(200);
}
@Override
public long succeedRetention() {
return 1000;
}
@Override
public long failingRetention() {
return 0;
}
@Override
public int refreshHeight() {
return DensityUtil.dip2px(100);
}
@Override
public int maxOffsetHeight() {
return DensityUtil.dip2px(150);
}
@Override
public void onReset(@NotNull KRefreshLayout refreshLayout) {
}
@Override
public void onPrepare(@NotNull KRefreshLayout refreshLayout) {
}
@Override
public void onRefresh(@NotNull KRefreshLayout refreshLayout) {
mHeader.releaseDrag();
}
@Override
public void onComplete(@NotNull KRefreshLayout refreshLayout, boolean isSuccess) {
mHeader.setRefreshing(false);
}
@Override
public void onScroll(@NotNull KRefreshLayout refreshLayout, int distance, float percent, boolean refreshing) {
if (!refreshing){
mHeader.getLayoutParams().height = distance;
mHeader.requestLayout();
}
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/fungame/BattleCityView.java
================================================
package gorden.krefreshlayout.demo.header.fungame;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.SparseArray;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
/**
* Created by Hitomis on 2016/3/09.
* email:196425254@qq.com
*/
public class BattleCityView extends FunGameView {
/**
* 轨道数量
*/
private static int TANK_ROW_NUM = 3;
/**
* 炮管尺寸所在tank尺寸的比率
*/
private static final float TANK_BARREL_RATIO = 1/3.f;
/**
* 默认子弹之间空隙间距
*/
private static final int DEFAULT_BULLET_NUM_SPACING = 360;
/**
* 默认敌方坦克之间间距
*/
private static final int DEFAULT_ENEMY_TANK_NUM_SPACING = 60;
/**
* 表示运行漏掉的敌方坦克总数量 和 升级后消灭坦克总数量的增量
*/
private static final int DEFAULT_TANK_MAGIC_TOTAL_NUM = 8;
/**
* 所有轨道上敌方坦克矩阵集合
*/
private SparseArray> eTankSparseArray;
/**
* 屏幕上所有子弹坐标点集合
*/
private Queue mBulletList;
/**
* 击中敌方坦克的子弹坐标点
*/
private Point usedBullet;
/**
* 用于随机定位一个轨道下标值
*/
private Random random;
/**
* 子弹半径
*/
private float bulletRadius;
/**
* 敌方坦克间距、子弹间距
*/
private int enemyTankSpace, bulletSpace;
/**
* 炮筒尺寸
*/
private int barrelSize;
/**
* 敌方坦克速度、子弹速度
*/
private int enemySpeed = 2, bulletSpeed = 7;
/**
* 当前前一辆敌方坦克和后一辆已经存在的间距值
* 用于确定是否要派出新的一辆敌方坦克
*/
private int offsetETankX;
/**
* 当前前一颗子弹和后一颗子弹的间距值
* 用于确定是否要发射新的一颗子弹
*/
private int offsetMBulletX;
/**
* 当前漏掉的坦克数量
*/
private int overstepNum;
/**
* 当前难度等级需要消灭坦克数量
*/
private int levelNum;
/**
* 当前难度等级内消灭的敌方坦克数量
*/
private int wipeOutNum;
/**
* 表示第一次标示值,用于添加第一辆敌方坦克逻辑
*/
private boolean once = true;
public BattleCityView(Context context) {
this(context, null);
}
public BattleCityView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BattleCityView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void initConcreteView() {
random = new Random();
controllerSize = (int) (Math.floor((screenHeight * VIEW_HEIGHT_RATIO - (TANK_ROW_NUM + 1) * DIVIDING_LINE_SIZE) / TANK_ROW_NUM + .5f));
barrelSize = (int) Math.floor(controllerSize * TANK_BARREL_RATIO + .5f);
bulletRadius = (barrelSize - 2 * DIVIDING_LINE_SIZE) * .5f;
resetConfigParams();
}
@Override
protected void drawGame(Canvas canvas) {
drawSelfTank(canvas);
if (status == STATUS_GAME_PLAY || status == STATUS_GAME_FINISHED) {
drawEnemyTank(canvas);
makeBulletPath(canvas);
}
}
@Override
protected void resetConfigParams() {
controllerPosition = DIVIDING_LINE_SIZE;
status = FunGameView.STATUS_GAME_PREPAR;
enemySpeed = 2;
bulletSpeed = 7;
levelNum = DEFAULT_TANK_MAGIC_TOTAL_NUM;
wipeOutNum = 0;
once = true;
enemyTankSpace = controllerSize + barrelSize + DEFAULT_ENEMY_TANK_NUM_SPACING;
bulletSpace = DEFAULT_BULLET_NUM_SPACING;
eTankSparseArray = new SparseArray<>();
for (int i = 0; i < TANK_ROW_NUM; i++) {
Queue rectFQueue = new LinkedList<>();
eTankSparseArray.put(i, rectFQueue);
}
mBulletList = new LinkedList<>();
}
/**
* 由index轨道下标从左边起始位置生成一个用于绘制敌方坦克的Rect
* @param index 轨道下标
* @return 敌方坦克矩阵
*/
private RectF generateEnemyTank(int index) {
float left = - (controllerSize + barrelSize);
float top = index * (controllerSize + DIVIDING_LINE_SIZE) + DIVIDING_LINE_SIZE;
return new RectF(left, top, left + barrelSize * 2.5f, top + controllerSize);
}
/**
* 绘制子弹路径
* @param canvas 默认画布
*/
private void makeBulletPath(Canvas canvas) {
mPaint.setColor(mModelColor);
offsetMBulletX += bulletSpeed;
if (offsetMBulletX / bulletSpace == 1) {
offsetMBulletX = 0;
}
if (offsetMBulletX == 0) {
Point bulletPoint = new Point();
bulletPoint.x = screenWidth - controllerSize - barrelSize;
bulletPoint.y = (int) (controllerPosition + controllerSize * .5f);
mBulletList.offer(bulletPoint);
}
boolean isOversetp = false;
for (Point point : mBulletList) {
if (checkWipeOutETank(point)) {
usedBullet = point;
continue;
}
if (point.x + bulletRadius <= 0) {
isOversetp = true;
}
drawBullet(canvas, point);
}
if (isOversetp) {
mBulletList.poll();
}
mBulletList.remove(usedBullet);
usedBullet = null;
}
/**
* 由Y坐标获取该坐标所在轨道的下标
* @param y 坐标Y值
* @return 轨道下标
*/
private int getTrackIndex(int y) {
int index = y / (getMeasuredHeight() / TANK_ROW_NUM);
index = index >= TANK_ROW_NUM ? TANK_ROW_NUM - 1 : index;
index = index < 0 ? 0 : index;
return index;
}
/**
* 判断是否消灭敌方坦克
* @param point 单签子弹坐标点
* @return 消灭:true, 反之:false
*/
private boolean checkWipeOutETank(Point point) {
boolean beHit = false;
int trackIndex = getTrackIndex(point.y);
RectF rectF = eTankSparseArray.get(trackIndex).peek();
if (rectF != null && rectF.contains(point.x, point.y)) { // 击中
if (++wipeOutNum == levelNum) {
upLevel();
}
eTankSparseArray.get(trackIndex).poll();
beHit = true;
}
return beHit;
}
/**
* 难度升级
*/
private void upLevel() {
levelNum += DEFAULT_TANK_MAGIC_TOTAL_NUM;
enemySpeed++;
bulletSpeed += 2;
wipeOutNum = 0;
if (enemyTankSpace > 12)
enemyTankSpace -= 12;
if (bulletSpace > 30)
bulletSpace -= 30;
}
/**
* 绘制子弹
* @param canvas 默认画布
* @param point 子弹圆心坐标点
*/
private void drawBullet(Canvas canvas, Point point) {
point.x -= bulletSpeed;
canvas.drawCircle(point.x, point.y, bulletRadius, mPaint);
}
/**
* 判断我方坦克是否与敌方坦克相撞
* @param index 轨道下标
* @param selfX 我方坦克所在坐标X值
* @param selfY 我方坦克矩阵的top 或者 bottom 值
* @return true:相撞,反之:false
*/
private boolean checkTankCrash(int index, float selfX, float selfY) {
boolean isCrash = false;
RectF rectF = eTankSparseArray.get(index).peek();
if (rectF != null && rectF.contains(selfX, selfY)) {
isCrash = true;
}
return isCrash;
}
/**
* 绘制我方坦克
* @param canvas 默认画布
*/
private void drawSelfTank(Canvas canvas) {
mPaint.setColor(rModelColor);
boolean isAboveCrash = checkTankCrash(getTrackIndex((int) controllerPosition),
screenWidth - controllerSize,
controllerPosition);
boolean isBelowCrash = checkTankCrash(getTrackIndex((int) (controllerPosition + controllerSize)),
screenWidth - controllerSize,
controllerPosition + controllerSize);
if (isAboveCrash || isBelowCrash) {
status = STATUS_GAME_OVER;
}
canvas.drawRect(screenWidth - controllerSize,
controllerPosition,
screenWidth,
controllerPosition + controllerSize,
mPaint);
canvas.drawRect(screenWidth - controllerSize - barrelSize,
controllerPosition + (controllerSize - barrelSize) * .5f,
screenWidth - controllerSize,
controllerPosition + (controllerSize - barrelSize) * .5f + barrelSize,
mPaint);
}
/**
* 绘制三条轨道上的敌方坦克
* @param canvas 默认画布
*/
private void drawEnemyTank(Canvas canvas) {
mPaint.setColor(lModelColor);
offsetETankX += enemySpeed;
if (offsetETankX / enemyTankSpace == 1 || once) {
offsetETankX = 0;
once = false;
}
boolean isOverstep = false;
int option = apperanceOption();
for (int i = 0; i < TANK_ROW_NUM; i++) {
Queue rectFQueue = eTankSparseArray.get(i);
if (offsetETankX == 0 && i == option) {
rectFQueue.offer(generateEnemyTank(i));
}
for (RectF rectF : rectFQueue) {
if (rectF.left >= screenWidth) {
isOverstep = true;
if (++overstepNum >= DEFAULT_TANK_MAGIC_TOTAL_NUM) {
status = STATUS_GAME_OVER;
break;
}
continue;
}
drawTank(canvas, rectF);
}
if (status == STATUS_GAME_OVER) break;
if (isOverstep) {
rectFQueue.poll();
isOverstep = false;
}
}
invalidate();
}
/**
* 绘制一辆敌方坦克
* @param canvas 默认画布
* @param rectF 坦克矩阵
*/
private void drawTank(Canvas canvas, RectF rectF) {
rectF.set(rectF.left + enemySpeed, rectF.top, rectF.right + enemySpeed, rectF.bottom);
canvas.drawRect(rectF, mPaint);
float barrelTop = rectF.top + (controllerSize - barrelSize) * .5f;
canvas.drawRect(rectF.right, barrelTop, rectF.right + barrelSize, barrelTop + barrelSize, mPaint);
}
/**
* 随机定位一个轨道下标值
* @return 轨道下标
*/
private int apperanceOption() {
return random.nextInt(TANK_ROW_NUM);
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/fungame/FunGameFactory.java
================================================
package gorden.krefreshlayout.demo.header.fungame;
import android.content.Context;
import android.util.AttributeSet;
/**
* Created by Hitomis on 2016/3/10.
* email:196425254@qq.com
*/
public class FunGameFactory {
// Refused to use enum
static final int HITBLOCK = 0;
static final int BATTLECITY = 1;
static FunGameView createFunGameView(Context context, AttributeSet attributeSet, int type) {
FunGameView funGameView = null;
switch (type) {
case HITBLOCK:
funGameView = new HitBlockView(context, attributeSet);
break;
case BATTLECITY:
funGameView = new BattleCityView(context, attributeSet);
break;
default:
funGameView = new HitBlockView(context, attributeSet);
}
return funGameView;
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/fungame/FunGameHeader.java
================================================
package gorden.krefreshlayout.demo.header.fungame;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.jetbrains.annotations.NotNull;
import gorden.refresh.KRefreshHeader;
import gorden.refresh.KRefreshLayout;
/**
* Created by Hitomis on 2016/3/1.
*/
public class FunGameHeader extends FrameLayout implements KRefreshHeader {
private Context mContext;
private int headerType;
private FunGameView funGameView;
private RelativeLayout curtainReLayout, maskReLayout;
private TextView topMaskView, bottomMaskView;
private int halfHitBlockHeight;
private boolean isStart = false;
private String topMaskViewText = "Pull To Break Out!";
private String bottomMaskViewText = "Scrooll to move handle";
private String loadingText = "Loading...";
private String loadingFinishedText = "Loading Finished";
private String gameOverText = "Game Over";
private int topMaskTextSize = 16;
private int bottomMaskTextSize = 16;
public FunGameHeader(Context context) {
this(context, null);
}
public FunGameHeader(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FunGameHeader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
headerType = FunGameFactory.HITBLOCK;
initView(attrs);
}
private void initView(AttributeSet attrs) {
funGameView = FunGameFactory.createFunGameView(mContext, attrs, headerType);
setHeaderLodingStr(loadingText);
setHeaderLoadingFinishedStr(loadingFinishedText);
setHeaderGameOverStr(gameOverText);
funGameView.postStatus(FunGameView.STATUS_GAME_PREPAR);
addView(funGameView);
curtainReLayout = new RelativeLayout(mContext);
maskReLayout = new RelativeLayout(mContext);
maskReLayout.setBackgroundColor(Color.parseColor("#3A3A3A"));
topMaskView = createMaskTextView(topMaskViewText, topMaskTextSize, Gravity.BOTTOM);
bottomMaskView = createMaskTextView(bottomMaskViewText, bottomMaskTextSize, Gravity.TOP);
coverMaskView();
funGameView.getViewTreeObserver().addOnGlobalLayoutListener(new MeasureListener());
}
private TextView createMaskTextView(String text, int textSize, int gravity) {
TextView maskTextView = new TextView(mContext);
maskTextView.setTextColor(Color.BLACK);
maskTextView.setBackgroundColor(Color.WHITE);
maskTextView.setGravity(gravity | Gravity.CENTER_HORIZONTAL);
maskTextView.setTextSize(textSize);
maskTextView.setText(text);
return maskTextView;
}
private void coverMaskView() {
LayoutParams maskLp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
maskLp.topMargin = (int) FunGameView.DIVIDING_LINE_SIZE;
maskLp.bottomMargin = (int) FunGameView.DIVIDING_LINE_SIZE;
addView(maskReLayout, maskLp);
addView(curtainReLayout, maskLp);
}
@Override
public long succeedRetention() {
return 200;
}
@Override
public long failingRetention() {
return 0;
}
@Override
public int refreshHeight() {
return getHeight();
}
@Override
public int maxOffsetHeight() {
return 2 * getHeight();
}
@Override
public void onReset(@NotNull KRefreshLayout refreshLayout) {
postEnd();
}
@Override
public void onPrepare(@NotNull KRefreshLayout refreshLayout) {
}
@Override
public void onRefresh(@NotNull KRefreshLayout refreshLayout) {
postStart();
}
@Override
public void onComplete(@NotNull KRefreshLayout refreshLayout, boolean isSuccess) {
postComplete();
}
@Override
public void onScroll(@NotNull KRefreshLayout refreshLayout, int distance, float percent, boolean refreshing) {
if (refreshing) {
moveRacket(distance - refreshHeight());
}
}
private class MeasureListener implements ViewTreeObserver.OnGlobalLayoutListener {
@Override
public void onGlobalLayout() {
halfHitBlockHeight = (int) ((funGameView.getHeight() - 2 * FunGameView.DIVIDING_LINE_SIZE) * .5f);
RelativeLayout.LayoutParams topRelayLayoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, halfHitBlockHeight);
RelativeLayout.LayoutParams bottomRelayLayoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, halfHitBlockHeight);
bottomRelayLayoutParams.topMargin = halfHitBlockHeight;
curtainReLayout.removeAllViews();
curtainReLayout.addView(topMaskView, 0, topRelayLayoutParams);
curtainReLayout.addView(bottomMaskView, 1, bottomRelayLayoutParams);
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
private void doStart(long delay) {
ObjectAnimator topMaskAnimator = ObjectAnimator.ofFloat(topMaskView, "translationY", topMaskView.getTranslationY(), -halfHitBlockHeight);
ObjectAnimator bottomMaskAnimator = ObjectAnimator.ofFloat(bottomMaskView, "translationY", bottomMaskView.getTranslationY(), halfHitBlockHeight);
ObjectAnimator maskShadowAnimator = ObjectAnimator.ofFloat(maskReLayout, "alpha", maskReLayout.getAlpha(), 0);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(topMaskAnimator).with(bottomMaskAnimator).with(maskShadowAnimator);
animatorSet.setDuration(800);
animatorSet.setStartDelay(delay);
animatorSet.start();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
topMaskView.setVisibility(View.GONE);
bottomMaskView.setVisibility(View.GONE);
maskReLayout.setVisibility(View.GONE);
funGameView.postStatus(FunGameView.STATUS_GAME_PLAY);
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = 0, height = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
if (childView instanceof FunGameView) {
width = childView.getMeasuredWidth();
height = childView.getMeasuredHeight();
}
}
if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.EXACTLY) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void postStart() {
if (!isStart) {
doStart(200);
isStart = true;
}
}
public void postEnd() {
isStart = false;
funGameView.postStatus(FunGameView.STATUS_GAME_PREPAR);
topMaskView.setTranslationY(topMaskView.getTranslationY() + halfHitBlockHeight);
bottomMaskView.setTranslationY(bottomMaskView.getTranslationY() - halfHitBlockHeight);
maskReLayout.setAlpha(1.f);
topMaskView.setVisibility(View.VISIBLE);
bottomMaskView.setVisibility(View.VISIBLE);
maskReLayout.setVisibility(View.VISIBLE);
}
public void postComplete() {
funGameView.postStatus(FunGameView.STATUS_GAME_FINISHED);
}
public void moveRacket(float distance) {
if (isStart)
funGameView.moveController(distance);
}
public void back2StartPoint(long duration) {
funGameView.moveController2StartPoint(duration);
}
public int getGameStatus() {
return funGameView.getCurrStatus();
}
public void setTopMaskViewText(String topMaskViewText) {
this.topMaskViewText = topMaskViewText;
topMaskView.setText(topMaskViewText);
}
public void setBottomMaskViewText(String bottomMaskViewText) {
this.bottomMaskViewText = bottomMaskViewText;
bottomMaskView.setText(bottomMaskViewText);
}
public void setHeaderLodingStr(String loadingStr) {
funGameView.setTextLoading(loadingStr);
}
public void setHeaderGameOverStr(String gameOverStr) {
funGameView.setTextGameOver(gameOverStr);
}
public void setHeaderLoadingFinishedStr(String loadingFinishedStr) {
funGameView.setTextLoadingFinished(loadingFinishedStr);
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/fungame/FunGameView.java
================================================
package gorden.krefreshlayout.demo.header.fungame;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
/**
* Created by Hitomis on 2016/3/9.
* email:196425254@qq.com
*/
abstract class FunGameView extends View {
static final int STATUS_GAME_PREPAR = 0;
static final int STATUS_GAME_PLAY = 1;
static final int STATUS_GAME_OVER = 2;
static final int STATUS_GAME_FINISHED = 3;
/**
* 分割线默认宽度大小
*/
static final float DIVIDING_LINE_SIZE = 1.f;
/**
* 控件高度占屏幕高度比率
*/
static final float VIEW_HEIGHT_RATIO = .161f;
private String textGameOver;
private String textLoading;
private String textLoadingFinished;
protected Paint mPaint;
protected TextPaint textPaint;
protected float controllerPosition;
protected int controllerSize;
protected int screenWidth, screenHeight;
protected int status = STATUS_GAME_PREPAR;
protected int lModelColor, rModelColor, mModelColor;
public FunGameView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
lModelColor =Color.rgb(0, 0, 0);
mModelColor =Color.BLACK;
rModelColor =Color.parseColor("#A5A5A5");
initBaseTools();
initBaseConfigParams(context);
initConcreteView();
}
protected void initBaseTools() {
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(Color.parseColor("#C1C2C2"));
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeWidth(1.f);
}
protected void initBaseConfigParams(Context context) {
controllerPosition = DIVIDING_LINE_SIZE;
screenWidth = getScreenMetrics(context).widthPixels;
screenHeight = getScreenMetrics(context).heightPixels;
}
protected abstract void initConcreteView();
protected abstract void drawGame(Canvas canvas);
protected abstract void resetConfigParams();
/**
* 绘制分割线
* @param canvas 默认画布
*/
private void drawBoundary(Canvas canvas) {
mPaint.setColor(Color.parseColor("#606060"));
canvas.drawLine(0, 0, screenWidth, 0, mPaint);
canvas.drawLine(0, getMeasuredHeight(), screenWidth, getMeasuredHeight(), mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(screenWidth, (int) (screenHeight * VIEW_HEIGHT_RATIO));
}
@Override
protected void onDraw(Canvas canvas) {
drawBoundary(canvas);
drawText(canvas);
drawGame(canvas);
}
/**
* 绘制文字内容
* @param canvas 默认画布
*/
private void drawText(Canvas canvas) {
switch (status) {
case STATUS_GAME_PREPAR:
case STATUS_GAME_PLAY:
textPaint.setTextSize(50);
promptText(canvas, textLoading);
break;
case STATUS_GAME_FINISHED:
textPaint.setTextSize(40);
promptText(canvas, textLoadingFinished);
break;
case STATUS_GAME_OVER:
textPaint.setTextSize(50);
promptText(canvas, textGameOver);
break;
}
}
/**
* 提示文字信息
* @param canvas 默认画布
* @param text 相关文字字符串
*/
private void promptText(Canvas canvas, String text) {
float textX = (canvas.getWidth() - textPaint.measureText(text)) * .5f;
float textY = canvas.getHeight() * .5f - (textPaint.ascent() + textPaint.descent()) * .5f;
canvas.drawText(text, textX, textY, textPaint);
}
/**
* 移动控制器(控制器对象为具体控件中的右边图像模型)
* @param distance 移动的距离
*/
public void moveController(float distance) {
float maxDistance = (getMeasuredHeight() - 2 * DIVIDING_LINE_SIZE - controllerSize);
if (distance > maxDistance) {
distance = maxDistance;
}
controllerPosition = distance;
postInvalidate();
}
/**
* 移动控制器到起点位置
* @param duration
*/
public void moveController2StartPoint(long duration) {
ValueAnimator moveAnimator = ValueAnimator.ofFloat(controllerPosition, DIVIDING_LINE_SIZE);
moveAnimator.setDuration(duration);
moveAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
moveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
controllerPosition = Float.parseFloat(animation.getAnimatedValue().toString());
postInvalidate();
}
});
moveAnimator.start();
}
/**
* 更新当前控件状态
* @param status 状态码
*/
public void postStatus(int status) {
this.status = status;
if (status == STATUS_GAME_PREPAR) {
resetConfigParams();
}
postInvalidate();
}
/**
* 获取当前控件状态
* @return
*/
public int getCurrStatus() {
return status;
}
public String getTextGameOver() {
return textGameOver;
}
public void setTextGameOver(String textGameOver) {
this.textGameOver = textGameOver;
}
public String getTextLoading() {
return textLoading;
}
public void setTextLoading(String textLoading) {
this.textLoading = textLoading;
}
public String getTextLoadingFinished() {
return textLoadingFinished;
}
public void setTextLoadingFinished(String textLoadingFinished) {
this.textLoadingFinished = textLoadingFinished;
}
/**
* 获取屏幕尺寸
*
* @param context context
* @return 手机屏幕尺寸
*/
private DisplayMetrics getScreenMetrics(Context context) {
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(dm);
return dm;
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/fungame/HitBlockView.java
================================================
package gorden.krefreshlayout.demo.header.fungame;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Hitomis on 2016/2/29.
* email:196425254@qq.com
*/
public class HitBlockView extends FunGameView {
/**
* 默认矩形块竖向排列的数目
*/
private static final int BLOCK_VERTICAL_NUM = 5;
/**
* 默认矩形块横向排列的数目
*/
private static final int BLOCK_HORIZONTAL_NUM = 3;
/**
* 矩形块的高度占屏幕高度比率
*/
private static final float BLOCK_HEIGHT_RATIO = .03125f;
/**
* 矩形块的宽度占屏幕宽度比率
*/
private static final float BLOCK_WIDTH_RATIO = .01806f;
/**
* 挡板所在位置占屏幕宽度的比率
*/
private static final float RACKET_POSITION_RATIO = .8f;
/**
* 矩形块所在位置占屏幕宽度的比率
*/
private static final float BLOCK_POSITION_RATIO = .08f;
/**
* 小球默认其实弹射角度
*/
private static final int DEFAULT_ANGLE = 30;
/**
* 分割线默认宽度大小
*/
static final float DIVIDING_LINE_SIZE = 1.f;
/**
* 小球移动速度
*/
private static final int SPEED = 6;
/**
* 矩形砖块的高度、宽度
*/
private float blockHeight, blockWidth;
/**
* 小球半径
*/
private static final float BALL_RADIUS = 8.f;
private Paint blockPaint;
private float blockLeft, racketLeft;
private float cx, cy;
private List pointList;
private boolean isleft;
private int angle;
private int blockHorizontalNum;
private int speed;
public HitBlockView(Context context) {
this(context, null);
}
public HitBlockView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HitBlockView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initAttrs(context, attrs);
}
private void initAttrs(Context context, AttributeSet attrs) {
blockHorizontalNum = BLOCK_HORIZONTAL_NUM;
speed = SPEED;
}
@Override
protected void initConcreteView() {
blockPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
blockPaint.setStyle(Paint.Style.FILL);
blockHeight = screenHeight * BLOCK_HEIGHT_RATIO;
blockWidth = screenWidth * BLOCK_WIDTH_RATIO;
blockLeft = screenWidth * BLOCK_POSITION_RATIO;
racketLeft = screenWidth * RACKET_POSITION_RATIO;
controllerSize = (int) (blockHeight * 1.6f);
}
@Override
protected void drawGame(Canvas canvas) {
drawColorBlock(canvas);
drawRacket(canvas);
if (status == STATUS_GAME_PLAY || status == STATUS_GAME_FINISHED)
makeBallPath(canvas);
}
@Override
protected void resetConfigParams() {
cx = racketLeft - 2 * BALL_RADIUS;
cy = (int) (getHeight() * .5f);
controllerPosition = DIVIDING_LINE_SIZE;
angle = DEFAULT_ANGLE;
isleft = true;
if (pointList == null) {
pointList = new ArrayList<>();
} else {
pointList.clear();
}
}
/**
* 绘制挡板
* @param canvas 默认画布
*/
private void drawRacket(Canvas canvas) {
mPaint.setColor(rModelColor);
canvas.drawRect(racketLeft, controllerPosition, racketLeft + blockWidth, controllerPosition + controllerSize, mPaint);
}
/**
* 绘制并处理小球运动的轨迹
* @param canvas 默认画布
*/
private void makeBallPath(Canvas canvas) {
mPaint.setColor(mModelColor);
if (cx <= blockLeft + blockHorizontalNum * blockWidth + (blockHorizontalNum - 1) * DIVIDING_LINE_SIZE + BALL_RADIUS) { // 小球进入到色块区域
if (checkTouchBlock(cx, cy)) { // 反弹回来
isleft = false;
}
}
if (cx <= blockLeft + BALL_RADIUS ) { // 小球穿过色块区域
isleft = false;
}
if (cx + BALL_RADIUS >= racketLeft && cx - BALL_RADIUS < racketLeft + blockWidth) { //小球当前坐标X值在挡板X值区域范围内
if (checkTouchRacket(cy)) { // 小球与挡板接触
if (pointList.size() == blockHorizontalNum * BLOCK_VERTICAL_NUM) { // 矩形块全部被消灭,游戏结束
status = STATUS_GAME_OVER;
return;
}
isleft = true;
}
} else if (cx > canvas.getWidth()) { // 小球超出挡板区域
status = STATUS_GAME_OVER;
}
if (cy <= BALL_RADIUS + DIVIDING_LINE_SIZE) { // 小球撞到上边界
angle = 180 - DEFAULT_ANGLE;
} else if (cy >= getMeasuredHeight() - BALL_RADIUS - DIVIDING_LINE_SIZE) { // 小球撞到下边界
angle = 180 + DEFAULT_ANGLE;
}
if (isleft) {
cx -= speed;
} else {
cx += speed;
}
cy -= (float) Math.tan(Math.toRadians(angle)) * speed;
canvas.drawCircle(cx, cy, BALL_RADIUS, mPaint);
invalidate();
}
/**
* 检查小球是否撞击到挡板
* @param y 小球当前坐标Y值
* @return 小球位于挡板Y值区域范围内:true,反之:false
*/
private boolean checkTouchRacket(float y) {
boolean flag = false;
float diffVal = y - controllerPosition;
if (diffVal >= 0 && diffVal <= controllerSize) { // 小球位于挡板Y值区域范围内
flag = true;
}
return flag;
}
/**
* 检查小球是否撞击到矩形块
* @param x 小球坐标X值
* @param y 小球坐标Y值
* @return 撞击到:true,反之:false
*/
private boolean checkTouchBlock(float x, float y) {
int columnX = (int) ((x - blockLeft - BALL_RADIUS - speed ) / blockWidth);
columnX = columnX == blockHorizontalNum ? columnX - 1 : columnX;
int rowY = (int) (y / blockHeight);
rowY = rowY == BLOCK_VERTICAL_NUM ? rowY - 1 : rowY;
Point p = new Point();
p.set(columnX, rowY);
boolean flag = false;
for (Point point : pointList) {
if (point.equals(p.x, p.y)) {
flag = true;
break;
}
}
if (!flag) {
pointList.add(p);
}
return !flag;
}
/**
* 绘制矩形色块
* @param canvas 默认画布
*/
private void drawColorBlock(Canvas canvas) {
float left, top;
int column, row, redCode, greenCode, blueCode;
for (int i = 0; i < blockHorizontalNum * BLOCK_VERTICAL_NUM; i++) {
row = i / blockHorizontalNum;
column = i % blockHorizontalNum;
boolean flag = false;
for (Point point : pointList) {
if (point.equals(column, row)) {
flag = true;
break;
}
}
if (flag) {
continue;
}
redCode = 255 - (255 - Color.red(lModelColor)) / (column + 1);
greenCode = 255 - (255 - Color.green(lModelColor)) / (column + 1);
blueCode = 255 - (255 - Color.blue(lModelColor)) / (column + 1);
blockPaint.setColor(Color.rgb(redCode, greenCode, blueCode));
left = blockLeft + column * (blockWidth + DIVIDING_LINE_SIZE);
top = DIVIDING_LINE_SIZE + row * (blockHeight + DIVIDING_LINE_SIZE);
canvas.drawRect(left, top, left + blockWidth, top + blockHeight, blockPaint);
}
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/materia/CircleImageView.java
================================================
package gorden.krefreshlayout.demo.header.materia;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.support.v4.view.ViewCompat;
import android.view.animation.Animation;
import android.widget.ImageView;
/**
* Private class created to work around issues with AnimationListeners being
* called before the animation is actually complete and support shadows on older
* platforms.
*
*/
public class CircleImageView extends ImageView {
private static final int KEY_SHADOW_COLOR = 0x1E000000;
private static final int FILL_SHADOW_COLOR = 0x3D000000;
// PX
private static final float X_OFFSET = 0f;
private static final float Y_OFFSET = 1.75f;
private static final float SHADOW_RADIUS = 3.5f;
private static final int SHADOW_ELEVATION = 4;
private Animation.AnimationListener mListener;
private int mShadowRadius;
public CircleImageView(Context context, int color, final float radius) {
super(context);
final float density = getContext().getResources().getDisplayMetrics().density;
final int diameter = (int) (radius * density * 2);
final int shadowYOffset = (int) (density * Y_OFFSET);
final int shadowXOffset = (int) (density * X_OFFSET);
mShadowRadius = (int) (density * SHADOW_RADIUS);
ShapeDrawable circle;
if (elevationSupported()) {
circle = new ShapeDrawable(new OvalShape());
ViewCompat.setElevation(this, SHADOW_ELEVATION * density);
} else {
OvalShape oval = new OvalShadow(mShadowRadius, diameter);
circle = new ShapeDrawable(oval);
ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
KEY_SHADOW_COLOR);
final int padding = mShadowRadius;
// set padding so the inner image sits correctly within the shadow.
setPadding(padding, padding, padding, padding);
}
circle.getPaint().setColor(color);
setBackgroundDrawable(circle);
}
private boolean elevationSupported() {
return android.os.Build.VERSION.SDK_INT >= 21;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!elevationSupported()) {
setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight()
+ mShadowRadius*2);
}
}
public void setAnimationListener(Animation.AnimationListener listener) {
mListener = listener;
}
@Override
public void onAnimationStart() {
super.onAnimationStart();
if (mListener != null) {
mListener.onAnimationStart(getAnimation());
}
}
@Override
public void onAnimationEnd() {
super.onAnimationEnd();
if (mListener != null) {
mListener.onAnimationEnd(getAnimation());
}
}
/**
* Update the background color of the circle image view.
*
* @param colorRes Id of a color resource.
*/
public void setBackgroundColorRes(int colorRes) {
setBackgroundColor(getContext().getResources().getColor(colorRes));
}
@Override
public void setBackgroundColor(int color) {
if (getBackground() instanceof ShapeDrawable) {
((ShapeDrawable) getBackground()).getPaint().setColor(color);
}
}
private class OvalShadow extends OvalShape {
private RadialGradient mRadialGradient;
private Paint mShadowPaint;
private int mCircleDiameter;
public OvalShadow(int shadowRadius, int circleDiameter) {
super();
mShadowPaint = new Paint();
mShadowRadius = shadowRadius;
mCircleDiameter = circleDiameter;
mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2,
mShadowRadius, new int[] {
FILL_SHADOW_COLOR, Color.TRANSPARENT
}, null, Shader.TileMode.CLAMP);
mShadowPaint.setShader(mRadialGradient);
}
@Override
public void draw(Canvas canvas, Paint paint) {
final int viewWidth = CircleImageView.this.getWidth();
final int viewHeight = CircleImageView.this.getHeight();
canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius),
mShadowPaint);
canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint);
}
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/materia/MateriaProgressHeader.java
================================================
package gorden.krefreshlayout.demo.header.materia;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.jetbrains.annotations.NotNull;
import gorden.krefreshlayout.demo.R;
import gorden.refresh.KRefreshHeader;
import gorden.refresh.KRefreshLayout;
/**
* document
* Created by Gordn on 2017/6/20.
*/
public class MateriaProgressHeader extends FrameLayout implements KRefreshHeader {
private int mCircleWidth;
private int mCircleHeight;
private static final int CIRCLE_DIAMETER = 40;
private static final int CIRCLE_DIAMETER_LARGE = 56;
// Maps to ProgressBar.Large style
public static final int LARGE = MaterialProgressDrawable.LARGE;
// Maps to ProgressBar default style
public static final int DEFAULT = MaterialProgressDrawable.DEFAULT;
// Default background for the progress spinner
private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA;
// Default offset in dips from the top of the view to where the progress spinner should stop
private static final int DEFAULT_CIRCLE_TARGET = 64;
private static final float MAX_PROGRESS_ANGLE = .8f;
private static final int MAX_ALPHA = 255;
private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA);
private CircleImageView mCircleView;
private MaterialProgressDrawable mProgress;
public MateriaProgressHeader(@NonNull Context context) {
this(context, null);
}
public MateriaProgressHeader(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density);
createProgressView();
ViewCompat.setChildrenDrawingOrderEnabled(this, true);
setColorSchemeColors(getResources().getIntArray(R.array.refresh_color));
}
private void createProgressView() {
mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER / 2);
mProgress = new MaterialProgressDrawable(getContext(), this);
mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
mCircleView.setImageDrawable(mProgress);
mCircleView.setVisibility(View.GONE);
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
mCircleView.setLayoutParams(params);
addView(mCircleView);
}
/**
* Set the background color of the progress spinner disc.
*
* @param color
*/
public void setProgressBackgroundColorSchemeColor(@ColorInt int color) {
mCircleView.setBackgroundColor(color);
mProgress.setBackgroundColor(color);
}
public void setColorSchemeResources(@ColorRes int... colorResIds) {
final Resources res = getResources();
int[] colorRes = new int[colorResIds.length];
for (int i = 0; i < colorResIds.length; i++) {
colorRes[i] = res.getColor(colorResIds[i]);
}
setColorSchemeColors(colorRes);
}
public void setColorSchemeColors(int... colors) {
mProgress.setColorSchemeColors(colors);
}
public void setSize(int size) {
if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) {
return;
}
final DisplayMetrics metrics = getResources().getDisplayMetrics();
if (size == MaterialProgressDrawable.LARGE) {
mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);
} else {
mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
}
// force the bounds of the progress circle inside the circle view to
// update by setting it to null before updating its size and then
// re-setting it
mCircleView.setImageDrawable(null);
mProgress.updateSizes(size);
mCircleView.setImageDrawable(mProgress);
}
@Override
public long succeedRetention() {
return 200;
}
@Override
public long failingRetention() {
return 0;
}
@Override
public int refreshHeight() {
return getHeight();
}
@Override
public int maxOffsetHeight() {
return 2 * getHeight();
}
boolean isReset = true;
@Override
public void onReset(@NotNull KRefreshLayout refreshLayout) {
mCircleView.clearAnimation();
mCircleView.animate().cancel();
mProgress.stop();
mCircleView.setVisibility(View.GONE);
mCircleView.getBackground().setAlpha(MAX_ALPHA);
mProgress.setAlpha(MAX_ALPHA);
ViewCompat.setScaleX(mCircleView, 0);
ViewCompat.setScaleY(mCircleView, 0);
ViewCompat.setAlpha(mCircleView, 1);
isReset = true;
}
@Override
public void onPrepare(@NotNull KRefreshLayout refreshLayout) {
mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
}
@Override
public void onRefresh(@NotNull KRefreshLayout refreshLayout) {
mCircleView.setVisibility(View.VISIBLE);
mCircleView.getBackground().setAlpha(MAX_ALPHA);
mProgress.setAlpha(MAX_ALPHA);
ViewCompat.setScaleX(mCircleView, 1f);
ViewCompat.setScaleY(mCircleView, 1f);
mProgress.setArrowScale(1f);
mProgress.start();
isReset = false;
}
@Override
public void onComplete(@NotNull KRefreshLayout refreshLayout, boolean isSuccess) {
mCircleView.animate().scaleX(0).scaleY(0).alpha(0).start();
}
@Override
public void onScroll(@NotNull KRefreshLayout refreshLayout, int distance, float percent, boolean refreshing) {
if (!refreshing && isReset) {
if (mCircleView.getVisibility() != View.VISIBLE) {
mCircleView.setVisibility(View.VISIBLE);
}
if (percent >= 1f) {
ViewCompat.setScaleX(mCircleView, 1f);
ViewCompat.setScaleY(mCircleView, 1f);
} else {
ViewCompat.setScaleX(mCircleView, percent);
ViewCompat.setScaleY(mCircleView, percent);
}
if (percent <= 1f) {
mProgress.setAlpha((int) (STARTING_PROGRESS_ALPHA + (MAX_ALPHA - STARTING_PROGRESS_ALPHA) * percent));
}
float adjustedPercent = (float) Math.max(percent - .4, 0) * 5 / 3;
float strokeStart = adjustedPercent * .8f;
mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
mProgress.setArrowScale(Math.min(1f, adjustedPercent));
float rotation = (-0.25f + .4f * adjustedPercent) * .5f;
mProgress.setProgressRotation(rotation);
}
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/materia/MaterialProgressDrawable.java
================================================
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gorden.krefreshlayout.demo.header.materia;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
* Fancy progress indicator for Material theme.
*/
class MaterialProgressDrawable extends Drawable implements Animatable {
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
private static final float FULL_ROTATION = 1080.0f;
@Retention(RetentionPolicy.SOURCE)
@IntDef({LARGE, DEFAULT})
public @interface ProgressDrawableSize {}
// Maps to ProgressBar.Large style
static final int LARGE = 0;
// Maps to ProgressBar default style
static final int DEFAULT = 1;
// Maps to ProgressBar default style
private static final int CIRCLE_DIAMETER = 40;
private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width
private static final float STROKE_WIDTH = 2.5f;
// Maps to ProgressBar.Large style
private static final int CIRCLE_DIAMETER_LARGE = 56;
private static final float CENTER_RADIUS_LARGE = 12.5f;
private static final float STROKE_WIDTH_LARGE = 3f;
private static final int[] COLORS = new int[] {
Color.BLACK
};
/**
* The value in the linear interpolator for animating the drawable at which
* the color transition should start
*/
private static final float COLOR_START_DELAY_OFFSET = 0.75f;
private static final float END_TRIM_START_DELAY_OFFSET = 0.5f;
private static final float START_TRIM_DURATION_OFFSET = 0.5f;
/** The duration of a single progress spin in milliseconds. */
private static final int ANIMATION_DURATION = 1332;
/** The number of points in the progress "star". */
private static final float NUM_POINTS = 5f;
/** The list of animators operating on this drawable. */
private final ArrayList mAnimators = new ArrayList();
/** The indicator ring, used to manage animation state. */
private final Ring mRing;
/** Canvas rotation in degrees. */
private float mRotation;
/** Layout info for the arrowhead in dp */
private static final int ARROW_WIDTH = 10;
private static final int ARROW_HEIGHT = 5;
private static final float ARROW_OFFSET_ANGLE = 5;
/** Layout info for the arrowhead for the large spinner in dp */
private static final int ARROW_WIDTH_LARGE = 12;
private static final int ARROW_HEIGHT_LARGE = 6;
private static final float MAX_PROGRESS_ARC = .8f;
private Resources mResources;
private View mParent;
private Animation mAnimation;
float mRotationCount;
private double mWidth;
private double mHeight;
boolean mFinishing;
MaterialProgressDrawable(Context context, View parent) {
mParent = parent;
mResources = context.getResources();
mRing = new Ring(mCallback);
mRing.setColors(COLORS);
updateSizes(DEFAULT);
setupAnimators();
}
private void setSizeParameters(double progressCircleWidth, double progressCircleHeight,
double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) {
final Ring ring = mRing;
final DisplayMetrics metrics = mResources.getDisplayMetrics();
final float screenDensity = metrics.density;
mWidth = progressCircleWidth * screenDensity;
mHeight = progressCircleHeight * screenDensity;
ring.setStrokeWidth((float) strokeWidth * screenDensity);
ring.setCenterRadius(centerRadius * screenDensity);
ring.setColorIndex(0);
ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity);
ring.setInsets((int) mWidth, (int) mHeight);
}
/**
* Set the overall size for the progress spinner. This updates the radius
* and stroke width of the ring.
*
* @param size One of {@link MaterialProgressDrawable.LARGE} or
* {@link MaterialProgressDrawable.DEFAULT}
*/
public void updateSizes(@ProgressDrawableSize int size) {
if (size == LARGE) {
setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE,
STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE);
} else {
setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH,
ARROW_WIDTH, ARROW_HEIGHT);
}
}
/**
* @param show Set to true to display the arrowhead on the progress spinner.
*/
public void showArrow(boolean show) {
mRing.setShowArrow(show);
}
/**
* @param scale Set the scale of the arrowhead for the spinner.
*/
public void setArrowScale(float scale) {
mRing.setArrowScale(scale);
}
/**
* Set the start and end trim for the progress spinner arc.
*
* @param startAngle start angle
* @param endAngle end angle
*/
public void setStartEndTrim(float startAngle, float endAngle) {
mRing.setStartTrim(startAngle);
mRing.setEndTrim(endAngle);
}
/**
* Set the amount of rotation to apply to the progress spinner.
*
* @param rotation Rotation is from [0..1]
*/
public void setProgressRotation(float rotation) {
mRing.setRotation(rotation);
}
/**
* Update the background color of the circle image view.
*/
public void setBackgroundColor(int color) {
mRing.setBackgroundColor(color);
}
/**
* Set the colors used in the progress animation from color resources.
* The first color will also be the color of the bar that grows in response
* to a user swipe gesture.
*
* @param colors
*/
public void setColorSchemeColors(int... colors) {
mRing.setColors(colors);
mRing.setColorIndex(0);
}
@Override
public int getIntrinsicHeight() {
return (int) mHeight;
}
@Override
public int getIntrinsicWidth() {
return (int) mWidth;
}
@Override
public void draw(Canvas c) {
final Rect bounds = getBounds();
final int saveCount = c.save();
c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
mRing.draw(c, bounds);
c.restoreToCount(saveCount);
}
@Override
public void setAlpha(int alpha) {
mRing.setAlpha(alpha);
}
public int getAlpha() {
return mRing.getAlpha();
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mRing.setColorFilter(colorFilter);
}
@SuppressWarnings("unused")
void setRotation(float rotation) {
mRotation = rotation;
invalidateSelf();
}
@SuppressWarnings("unused")
private float getRotation() {
return mRotation;
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public boolean isRunning() {
final ArrayList animators = mAnimators;
final int N = animators.size();
for (int i = 0; i < N; i++) {
final Animation animator = animators.get(i);
if (animator.hasStarted() && !animator.hasEnded()) {
return true;
}
}
return false;
}
@Override
public void start() {
mAnimation.reset();
mRing.storeOriginals();
// Already showing some part of the ring
if (mRing.getEndTrim() != mRing.getStartTrim()) {
mFinishing = true;
mAnimation.setDuration(ANIMATION_DURATION / 2);
mParent.startAnimation(mAnimation);
} else {
mRing.setColorIndex(0);
mRing.resetOriginals();
mAnimation.setDuration(ANIMATION_DURATION);
mParent.startAnimation(mAnimation);
}
}
@Override
public void stop() {
mParent.clearAnimation();
setRotation(0);
mRing.setShowArrow(false);
mRing.setColorIndex(0);
mRing.resetOriginals();
}
float getMinProgressArc(Ring ring) {
return (float) Math.toRadians(
ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius()));
}
// Adapted from ArgbEvaluator.java
private int evaluateColorChange(float fraction, int startValue, int endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
| (int) ((startR + (int) (fraction * (endR - startR))) << 16)
| (int) ((startG + (int) (fraction * (endG - startG))) << 8)
| (int) ((startB + (int) (fraction * (endB - startB))));
}
/**
* Update the ring color if this is within the last 25% of the animation.
* The new ring color will be a translation from the starting ring color to
* the next color.
*/
void updateRingColor(float interpolatedTime, Ring ring) {
if (interpolatedTime > COLOR_START_DELAY_OFFSET) {
// scale the interpolatedTime so that the full
// transformation from 0 - 1 takes place in the
// remaining time
ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET)
/ (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(),
ring.getNextColor()));
}
}
void applyFinishTranslation(float interpolatedTime, Ring ring) {
// shrink back down and complete a full rotation before
// starting other circles
// Rotation goes between [0..1].
updateRingColor(interpolatedTime, ring);
float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)
+ 1f);
final float minProgressArc = getMinProgressArc(ring);
final float startTrim = ring.getStartingStartTrim()
+ (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim())
* interpolatedTime;
ring.setStartTrim(startTrim);
ring.setEndTrim(ring.getStartingEndTrim());
final float rotation = ring.getStartingRotation()
+ ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
ring.setRotation(rotation);
}
private void setupAnimators() {
final Ring ring = mRing;
final Animation animation = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
if (mFinishing) {
applyFinishTranslation(interpolatedTime, ring);
} else {
// The minProgressArc is calculated from 0 to create an
// angle that matches the stroke width.
final float minProgressArc = getMinProgressArc(ring);
final float startingEndTrim = ring.getStartingEndTrim();
final float startingTrim = ring.getStartingStartTrim();
final float startingRotation = ring.getStartingRotation();
updateRingColor(interpolatedTime, ring);
// Moving the start trim only occurs in the first 50% of a
// single ring animation
if (interpolatedTime <= START_TRIM_DURATION_OFFSET) {
// scale the interpolatedTime so that the full
// transformation from 0 - 1 takes place in the
// remaining time
final float scaledTime = (interpolatedTime)
/ (1.0f - START_TRIM_DURATION_OFFSET);
final float startTrim = startingTrim
+ ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR
.getInterpolation(scaledTime));
ring.setStartTrim(startTrim);
}
// Moving the end trim starts after 50% of a single ring
// animation completes
if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) {
// scale the interpolatedTime so that the full
// transformation from 0 - 1 takes place in the
// remaining time
final float minArc = MAX_PROGRESS_ARC - minProgressArc;
float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET)
/ (1.0f - START_TRIM_DURATION_OFFSET);
final float endTrim = startingEndTrim
+ (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime));
ring.setEndTrim(endTrim);
}
final float rotation = startingRotation + (0.25f * interpolatedTime);
ring.setRotation(rotation);
float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime)
+ (FULL_ROTATION * (mRotationCount / NUM_POINTS));
setRotation(groupRotation);
}
}
};
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.RESTART);
animation.setInterpolator(LINEAR_INTERPOLATOR);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mRotationCount = 0;
}
@Override
public void onAnimationEnd(Animation animation) {
// do nothing
}
@Override
public void onAnimationRepeat(Animation animation) {
ring.storeOriginals();
ring.goToNextColor();
ring.setStartTrim(ring.getEndTrim());
if (mFinishing) {
// finished closing the last ring from the swipe gesture; go
// into progress mode
mFinishing = false;
animation.setDuration(ANIMATION_DURATION);
ring.setShowArrow(false);
} else {
mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
}
}
});
mAnimation = animation;
}
private final Callback mCallback = new Callback() {
@Override
public void invalidateDrawable(Drawable d) {
invalidateSelf();
}
@Override
public void scheduleDrawable(Drawable d, Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(Drawable d, Runnable what) {
unscheduleSelf(what);
}
};
private static class Ring {
private final RectF mTempBounds = new RectF();
private final Paint mPaint = new Paint();
private final Paint mArrowPaint = new Paint();
private final Callback mCallback;
private float mStartTrim = 0.0f;
private float mEndTrim = 0.0f;
private float mRotation = 0.0f;
private float mStrokeWidth = 5.0f;
private float mStrokeInset = 2.5f;
private int[] mColors;
// mColorIndex represents the offset into the available mColors that the
// progress circle should currently display. As the progress circle is
// animating, the mColorIndex moves by one to the next available color.
private int mColorIndex;
private float mStartingStartTrim;
private float mStartingEndTrim;
private float mStartingRotation;
private boolean mShowArrow;
private Path mArrow;
private float mArrowScale;
private double mRingCenterRadius;
private int mArrowWidth;
private int mArrowHeight;
private int mAlpha;
private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mBackgroundColor;
private int mCurrentColor;
Ring(Callback callback) {
mCallback = callback;
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.STROKE);
mArrowPaint.setStyle(Style.FILL);
mArrowPaint.setAntiAlias(true);
}
public void setBackgroundColor(int color) {
mBackgroundColor = color;
}
/**
* Set the dimensions of the arrowhead.
*
* @param width Width of the hypotenuse of the arrow head
* @param height Height of the arrow point
*/
public void setArrowDimensions(float width, float height) {
mArrowWidth = (int) width;
mArrowHeight = (int) height;
}
/**
* Draw the progress spinner
*/
public void draw(Canvas c, Rect bounds) {
final RectF arcBounds = mTempBounds;
arcBounds.set(bounds);
arcBounds.inset(mStrokeInset, mStrokeInset);
final float startAngle = (mStartTrim + mRotation) * 360;
final float endAngle = (mEndTrim + mRotation) * 360;
float sweepAngle = endAngle - startAngle;
mPaint.setColor(mCurrentColor);
c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
drawTriangle(c, startAngle, sweepAngle, bounds);
if (mAlpha < 255) {
mCirclePaint.setColor(mBackgroundColor);
mCirclePaint.setAlpha(255 - mAlpha);
c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2,
mCirclePaint);
}
}
private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {
if (mShowArrow) {
if (mArrow == null) {
mArrow = new Path();
mArrow.setFillType(Path.FillType.EVEN_ODD);
} else {
mArrow.reset();
}
// Adjust the position of the triangle so that it is inset as
// much as the arc, but also centered on the arc.
float inset = (int) mStrokeInset / 2 * mArrowScale;
float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX());
float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY());
// Update the path each time. This works around an issue in SKIA
// where concatenating a rotation matrix to a scale matrix
// ignored a starting negative rotation. This appears to have
// been fixed as of API 21.
mArrow.moveTo(0, 0);
mArrow.lineTo(mArrowWidth * mArrowScale, 0);
mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight
* mArrowScale));
mArrow.offset(x - inset, y);
mArrow.close();
// draw a triangle
mArrowPaint.setColor(mCurrentColor);
c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),
bounds.exactCenterY());
c.drawPath(mArrow, mArrowPaint);
}
}
/**
* Set the colors the progress spinner alternates between.
*
* @param colors Array of integers describing the colors. Must be non-null.
*/
public void setColors(@NonNull int[] colors) {
mColors = colors;
// if colors are reset, make sure to reset the color index as well
setColorIndex(0);
}
/**
* Set the absolute color of the progress spinner. This is should only
* be used when animating between current and next color when the
* spinner is rotating.
*
* @param color int describing the color.
*/
public void setColor(int color) {
mCurrentColor = color;
}
/**
* @param index Index into the color array of the color to display in
* the progress spinner.
*/
public void setColorIndex(int index) {
mColorIndex = index;
mCurrentColor = mColors[mColorIndex];
}
/**
* @return int describing the next color the progress spinner should use when drawing.
*/
public int getNextColor() {
return mColors[getNextColorIndex()];
}
private int getNextColorIndex() {
return (mColorIndex + 1) % (mColors.length);
}
/**
* Proceed to the next available ring color. This will automatically
* wrap back to the beginning of colors.
*/
public void goToNextColor() {
setColorIndex(getNextColorIndex());
}
public void setColorFilter(ColorFilter filter) {
mPaint.setColorFilter(filter);
invalidateSelf();
}
/**
* @param alpha Set the alpha of the progress spinner and associated arrowhead.
*/
public void setAlpha(int alpha) {
mAlpha = alpha;
}
/**
* @return Current alpha of the progress spinner and arrowhead.
*/
public int getAlpha() {
return mAlpha;
}
/**
* @param strokeWidth Set the stroke width of the progress spinner in pixels.
*/
public void setStrokeWidth(float strokeWidth) {
mStrokeWidth = strokeWidth;
mPaint.setStrokeWidth(strokeWidth);
invalidateSelf();
}
@SuppressWarnings("unused")
public float getStrokeWidth() {
return mStrokeWidth;
}
@SuppressWarnings("unused")
public void setStartTrim(float startTrim) {
mStartTrim = startTrim;
invalidateSelf();
}
@SuppressWarnings("unused")
public float getStartTrim() {
return mStartTrim;
}
public float getStartingStartTrim() {
return mStartingStartTrim;
}
public float getStartingEndTrim() {
return mStartingEndTrim;
}
public int getStartingColor() {
return mColors[mColorIndex];
}
@SuppressWarnings("unused")
public void setEndTrim(float endTrim) {
mEndTrim = endTrim;
invalidateSelf();
}
@SuppressWarnings("unused")
public float getEndTrim() {
return mEndTrim;
}
@SuppressWarnings("unused")
public void setRotation(float rotation) {
mRotation = rotation;
invalidateSelf();
}
@SuppressWarnings("unused")
public float getRotation() {
return mRotation;
}
public void setInsets(int width, int height) {
final float minEdge = (float) Math.min(width, height);
float insets;
if (mRingCenterRadius <= 0 || minEdge < 0) {
insets = (float) Math.ceil(mStrokeWidth / 2.0f);
} else {
insets = (float) (minEdge / 2.0f - mRingCenterRadius);
}
mStrokeInset = insets;
}
@SuppressWarnings("unused")
public float getInsets() {
return mStrokeInset;
}
/**
* @param centerRadius Inner radius in px of the circle the progress
* spinner arc traces.
*/
public void setCenterRadius(double centerRadius) {
mRingCenterRadius = centerRadius;
}
public double getCenterRadius() {
return mRingCenterRadius;
}
/**
* @param show Set to true to show the arrow head on the progress spinner.
*/
public void setShowArrow(boolean show) {
if (mShowArrow != show) {
mShowArrow = show;
invalidateSelf();
}
}
/**
* @param scale Set the scale of the arrowhead for the spinner.
*/
public void setArrowScale(float scale) {
if (scale != mArrowScale) {
mArrowScale = scale;
invalidateSelf();
}
}
/**
* @return The amount the progress spinner is currently rotated, between [0..1].
*/
public float getStartingRotation() {
return mStartingRotation;
}
/**
* If the start / end trim are offset to begin with, store them so that
* animation starts from that offset.
*/
public void storeOriginals() {
mStartingStartTrim = mStartTrim;
mStartingEndTrim = mEndTrim;
mStartingRotation = mRotation;
}
/**
* Reset the progress spinner to default rotation, start and end angles.
*/
public void resetOriginals() {
mStartingStartTrim = 0;
mStartingEndTrim = 0;
mStartingRotation = 0;
setStartTrim(0);
setEndTrim(0);
setRotation(0);
}
private void invalidateSelf() {
mCallback.invalidateDrawable(null);
}
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/rentals/RentalsSunDrawable.java
================================================
package gorden.krefreshlayout.demo.header.rentals;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import gorden.krefreshlayout.demo.R;
import gorden.krefreshlayout.demo.util.DensityUtil;
public class RentalsSunDrawable extends Drawable implements Animatable {
private static final float SCALE_START_PERCENT = 0.3f;
private static final int ANIMATION_DURATION = 1000;
private final static float SKY_RATIO = 0.65f;
private static final float SKY_INITIAL_SCALE = 1.05f;
private final static float TOWN_RATIO = 0.22f;
private static final float TOWN_INITIAL_SCALE = 1.20f;
private static final float TOWN_FINAL_SCALE = 1.30f;
private static final float SUN_FINAL_SCALE = 0.75f;
private static final float SUN_INITIAL_ROTATE_GROWTH = 1.2f;
private static final float SUN_FINAL_ROTATE_GROWTH = 1.5f;
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
private View mParent;
private Matrix mMatrix;
private Animation mAnimation;
private int mTop;
private int mScreenWidth;
private int mSkyHeight;
private float mSkyTopOffset;
private float mSkyMoveOffset;
private int mTownHeight;
private float mTownInitialTopOffset;
private float mTownFinalTopOffset;
private float mTownMoveOffset;
private int mSunSize = 100;
private float mSunLeftOffset;
private float mSunTopOffset;
private float mPercent = 0.0f;
private float mRotate = 0.0f;
private Bitmap mSky;
private Bitmap mSun;
private Bitmap mTown;
private boolean isRefreshing = false;
private Context mContext;
private int mTotalDragDistance;
public RentalsSunDrawable(Context context, View parent) {
mContext = context;
mParent = parent;
mMatrix = new Matrix();
initiateDimens();
createBitmaps();
setupAnimations();
}
private Context getContext() {
return mContext;
}
private void initiateDimens() {
DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
mTotalDragDistance = DensityUtil.dip2px(120);
mScreenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
mSkyHeight = (int) (SKY_RATIO * mScreenWidth);
mSkyTopOffset = -(mSkyHeight * 0.28f);
mSkyMoveOffset = DensityUtil.dip2px(15);
mTownHeight = (int) (TOWN_RATIO * mScreenWidth);
mTownInitialTopOffset = (mTotalDragDistance - mTownHeight * TOWN_INITIAL_SCALE) + mTotalDragDistance * .42f;
mTownFinalTopOffset = (mTotalDragDistance - mTownHeight * TOWN_FINAL_SCALE) + mTotalDragDistance * .42f;
mTownMoveOffset = DensityUtil.dip2px(10);
mSunLeftOffset = 0.3f * (float) mScreenWidth;
mSunTopOffset = (mTotalDragDistance * 0.5f);
mTop = 0;
}
private void createBitmaps() {
mSky = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.sky);
mSky = Bitmap.createScaledBitmap(mSky, mScreenWidth, mSkyHeight, true);
mTown = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.buildings);
mTown = Bitmap.createScaledBitmap(mTown, mScreenWidth, (int) (mScreenWidth * TOWN_RATIO), true);
mSun = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.sun);
mSun = Bitmap.createScaledBitmap(mSun, mSunSize, mSunSize, true);
}
public void offsetTopAndBottom(int offset) {
mTop = offset;
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
final int saveCount = canvas.save();
canvas.translate(0, mTotalDragDistance - mTop);
drawSky(canvas);
drawSun(canvas);
drawTown(canvas);
canvas.restoreToCount(saveCount);
}
private void drawSky(Canvas canvas) {
Matrix matrix = mMatrix;
matrix.reset();
int y = Math.max(0, mTop - mTotalDragDistance);
// 0 ~ 1
float dragPercent = Math.min(1f, Math.abs(mPercent));
/** Change skyScale between {@link #SKY_INITIAL_SCALE} and 1.0f depending on {@link #mPercent} */
float skyScale;
float scalePercentDelta = dragPercent - SCALE_START_PERCENT;
/** less than {@link SCALE_START_PERCENT} will be {@link SKY_INITIAL_SCALE} */
if (scalePercentDelta > 0) {
/** will change from 0 ~ 1 **/
float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);
skyScale = SKY_INITIAL_SCALE - (SKY_INITIAL_SCALE - 1.0f) * scalePercent;
} else {
skyScale = SKY_INITIAL_SCALE;
}
float offsetX = -(mScreenWidth * skyScale - mScreenWidth) / 2.0f;
float offsetY = y + 50 + mSkyTopOffset // Offset canvas moving, goes lower when goes down
- mSkyHeight * (skyScale - 1.0f) / 2 // Offset sky scaling, lower than 0, will go greater when goes down
+ mSkyMoveOffset * dragPercent; // Give it a little move top -> bottom // will go greater when goes down
matrix.postScale(skyScale, skyScale);
matrix.postTranslate(offsetX, offsetY);
canvas.drawBitmap(mSky, matrix, null);
}
private void drawTown(Canvas canvas) {
Matrix matrix = mMatrix;
matrix.reset();
int y = Math.max(0, mTop - mTotalDragDistance);
float dragPercent = Math.min(1f, Math.abs(mPercent));
float townScale;
float townTopOffset;
float townMoveOffset;
float scalePercentDelta = dragPercent - SCALE_START_PERCENT;
if (scalePercentDelta > 0) {
/**
* Change townScale between {@link #TOWN_INITIAL_SCALE} and {@link #TOWN_FINAL_SCALE} depending on {@link #mPercent}
* Change townTopOffset between {@link #mTownInitialTopOffset} and {@link #mTownFinalTopOffset} depending on {@link #mPercent}
*/
float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);
townScale = TOWN_INITIAL_SCALE + (TOWN_FINAL_SCALE - TOWN_INITIAL_SCALE) * scalePercent;
townTopOffset = mTownInitialTopOffset - (mTownFinalTopOffset - mTownInitialTopOffset) * scalePercent;
townMoveOffset = mTownMoveOffset * (1.0f - scalePercent);
} else {
float scalePercent = dragPercent / SCALE_START_PERCENT;
townScale = TOWN_INITIAL_SCALE;
townTopOffset = mTownInitialTopOffset;
townMoveOffset = mTownMoveOffset * scalePercent;
}
float offsetX = -(mScreenWidth * townScale - mScreenWidth) / 2.0f;
// float offsetY = (1.0f - dragPercent) * mTotalDragDistance // Offset canvas moving
float offsetY = y +
+townTopOffset
- mTownHeight * (townScale - 1.0f) / 2 // Offset town scaling
+ townMoveOffset; // Give it a little move
matrix.postScale(townScale, townScale);
matrix.postTranslate(offsetX, offsetY);
canvas.drawBitmap(mTown, matrix, null);
}
private void drawSun(Canvas canvas) {
Matrix matrix = mMatrix;
matrix.reset();
float dragPercent = mPercent;
if (dragPercent > 1.0f) { // Slow down if pulling over set height
dragPercent = (dragPercent + 9.0f) / 10;
}
float sunRadius = (float) mSunSize / 2.0f;
float sunRotateGrowth = SUN_INITIAL_ROTATE_GROWTH;
float offsetX = mSunLeftOffset;
float offsetY = mSunTopOffset
+ (mTotalDragDistance / 2) * (1.0f - dragPercent); // Move the sun up
float scalePercentDelta = dragPercent - SCALE_START_PERCENT;
if (scalePercentDelta > 0) {
float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);
float sunScale = 1.0f - (1.0f - SUN_FINAL_SCALE) * scalePercent;
sunRotateGrowth += (SUN_FINAL_ROTATE_GROWTH - SUN_INITIAL_ROTATE_GROWTH) * scalePercent;
matrix.preTranslate(offsetX + (sunRadius - sunRadius * sunScale), offsetY * (2.0f - sunScale));
matrix.preScale(sunScale, sunScale);
offsetX += sunRadius;
offsetY = offsetY * (2.0f - sunScale) + sunRadius * sunScale;
} else {
matrix.postTranslate(offsetX, offsetY);
offsetX += sunRadius;
offsetY += sunRadius;
}
float r = (isRefreshing ? -360 : 360) * mRotate * (isRefreshing ? 1 : sunRotateGrowth);
matrix.postRotate(r, offsetX, offsetY);
canvas.drawBitmap(mSun, matrix, null);
}
public void setPercent(float percent) {
mPercent = percent;
setRotate(percent);
}
public void setRotate(float rotate) {
mRotate = rotate;
mParent.invalidate();
invalidateSelf();
}
public void resetOriginals() {
setPercent(0);
setRotate(0);
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, mSkyHeight + top);
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public boolean isRunning() {
return false;
}
@Override
public void start() {
mAnimation.reset();
isRefreshing = true;
mParent.startAnimation(mAnimation);
}
@Override
public void stop() {
mParent.clearAnimation();
isRefreshing = false;
resetOriginals();
}
private void setupAnimations() {
mAnimation = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
setRotate(interpolatedTime);
}
};
mAnimation.setRepeatCount(Animation.INFINITE);
mAnimation.setRepeatMode(Animation.RESTART);
mAnimation.setInterpolator(LINEAR_INTERPOLATOR);
mAnimation.setDuration(ANIMATION_DURATION);
}
public int getTotalDragDistance() {
return mTotalDragDistance;
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/rentals/RentalsSunHeaderView.java
================================================
package gorden.krefreshlayout.demo.header.rentals;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import org.jetbrains.annotations.NotNull;
import gorden.krefreshlayout.demo.util.DensityUtil;
import gorden.refresh.KRefreshHeader;
import gorden.refresh.KRefreshLayout;
/*
* 一个经典的太阳升起刷新Header
*/
public class RentalsSunHeaderView extends View implements KRefreshHeader {
private RentalsSunDrawable mDrawable;
private float mPercent;
private int mDistance;
public RentalsSunHeaderView(Context context) {
super(context);
init();
}
public RentalsSunHeaderView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public RentalsSunHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mDrawable = new RentalsSunDrawable(getContext(), this);
setPadding(0, DensityUtil.dip2px(15), 0, DensityUtil.dip2px(10));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = mDrawable.getTotalDragDistance() * 5 / 4;
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + getPaddingTop() + getPaddingBottom(), MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int pl = getPaddingLeft();
int pt = getPaddingTop();
mDrawable.setBounds(pl, pt, pl + right - left, pt + bottom - top);
}
@Override
public long succeedRetention() {
return 300;
}
@Override
public long failingRetention() {
return 0;
}
@Override
public int refreshHeight() {
return mDrawable.getTotalDragDistance()+DensityUtil.dip2px(15);
}
@Override
public int maxOffsetHeight() {
return getHeight();
}
@Override
public void onReset(@NotNull KRefreshLayout refreshLayout) {
mDrawable.resetOriginals();
}
@Override
protected void onDraw(Canvas canvas) {
mDrawable.draw(canvas);
}
@Override
public void onPrepare(@NotNull KRefreshLayout refreshLayout) {
}
@Override
public void onRefresh(@NotNull KRefreshLayout refreshLayout) {
mDrawable.start();
mDrawable.offsetTopAndBottom(mDistance);
mDrawable.setPercent(mPercent);
invalidate();
}
@Override
public void onComplete(@NotNull KRefreshLayout refreshLayout,boolean isSuccess) {
mDrawable.stop();
mDrawable.offsetTopAndBottom(mDistance);
mDrawable.setPercent(mPercent);
invalidate();
}
@Override
public void onScroll(@NotNull KRefreshLayout refreshLayout, int distance, float percent, boolean refreshing) {
mPercent = percent;
mDistance = distance;
mDrawable.offsetTopAndBottom(distance);
mDrawable.setPercent(percent);
invalidate();
}
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
if (drawable == mDrawable) {
invalidate();
} else {
super.invalidateDrawable(drawable);
}
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/storehouse/StoreHouseBarItem.java
================================================
package gorden.krefreshlayout.demo.header.storehouse;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import java.util.Random;
/**
* Created by srain on 11/6/14.
*/
public class StoreHouseBarItem extends Animation {
public PointF midPoint;
public float translationX;
public int index;
private final Paint mPaint = new Paint();
private float mFromAlpha = 1.0f;
private float mToAlpha = 0.4f;
private PointF mCStartPoint;
private PointF mCEndPoint;
public StoreHouseBarItem(int index, PointF start, PointF end, int color, int lineWidth) {
this.index = index;
midPoint = new PointF((start.x + end.x) / 2, (start.y + end.y) / 2);
mCStartPoint = new PointF(start.x - midPoint.x, start.y - midPoint.y);
mCEndPoint = new PointF(end.x - midPoint.x, end.y - midPoint.y);
setColor(color);
setLineWidth(lineWidth);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
}
public void setLineWidth(int width) {
mPaint.setStrokeWidth(width);
}
public void setColor(int color) {
mPaint.setColor(color);
}
public void resetPosition(int horizontalRandomness) {
Random random = new Random();
int randomNumber = -random.nextInt(horizontalRandomness) + horizontalRandomness;
translationX = randomNumber;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float alpha = mFromAlpha;
alpha = alpha + ((mToAlpha - alpha) * interpolatedTime);
setAlpha(alpha);
}
public void start(float fromAlpha, float toAlpha) {
mFromAlpha = fromAlpha;
mToAlpha = toAlpha;
super.start();
}
public void setAlpha(float alpha) {
mPaint.setAlpha((int) (alpha * 255));
}
public void draw(Canvas canvas) {
canvas.drawLine(mCStartPoint.x, mCStartPoint.y, mCEndPoint.x, mCEndPoint.y, mPaint);
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/storehouse/StoreHouseHeader.java
================================================
package gorden.krefreshlayout.demo.header.storehouse;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Transformation;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import gorden.krefreshlayout.demo.util.DensityUtil;
import gorden.refresh.KRefreshHeader;
import gorden.refresh.KRefreshLayout;
public class StoreHouseHeader extends View implements KRefreshHeader {
public ArrayList mItemList = new ArrayList();
private int mLineWidth = -1;
private float mScale = 1;
private int mDropHeight = -1;
private float mInternalAnimationFactor = 0.7f;
private int mHorizontalRandomness = -1;
private float mProgress = 0;
private int mDrawZoneWidth = 0;
private int mDrawZoneHeight = 0;
private int mOffsetX = 0;
private int mOffsetY = 0;
private float mBarDarkAlpha = 0.4f;
private float mFromAlpha = 1.0f;
private float mToAlpha = 0.4f;
private int mLoadingAniDuration = 1000;
private int mLoadingAniSegDuration = 1000;
private int mLoadingAniItemDuration = 400;
private Transformation mTransformation = new Transformation();
private boolean mIsInLoading = false;
private AniController mAniController = new AniController();
private int mTextColor = Color.WHITE;
public StoreHouseHeader(Context context) {
super(context);
initView();
}
public StoreHouseHeader(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public StoreHouseHeader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
mLineWidth = DensityUtil.dip2px(1);
mDropHeight = DensityUtil.dip2px(40);
mHorizontalRandomness =DensityUtil.appWidth() / 2;
initWithString("KREFRESHLAYOUT",25);
setPadding(0,20,0,20);
}
private void setProgress(float progress) {
mProgress = progress;
}
public int getLoadingAniDuration() {
return mLoadingAniDuration;
}
public void setLoadingAniDuration(int duration) {
mLoadingAniDuration = duration;
mLoadingAniSegDuration = duration;
}
public StoreHouseHeader setLineWidth(int width) {
mLineWidth = width;
for (int i = 0; i < mItemList.size(); i++) {
mItemList.get(i).setLineWidth(width);
}
return this;
}
public StoreHouseHeader setTextColor(int color) {
mTextColor = color;
for (int i = 0; i < mItemList.size(); i++) {
mItemList.get(i).setColor(color);
}
return this;
}
public StoreHouseHeader setDropHeight(int height) {
mDropHeight = height;
return this;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = getTopOffset() + mDrawZoneHeight + getBottomOffset();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mOffsetX = (getMeasuredWidth() - mDrawZoneWidth) / 2;
mOffsetY = getTopOffset();
mDropHeight = getTopOffset();
}
private int getTopOffset() {
return getPaddingTop() + DensityUtil.dip2px(10);
}
private int getBottomOffset() {
return getPaddingBottom() + DensityUtil.dip2px(10);
}
public void initWithString(String str) {
initWithString(str, 25);
}
public void initWithString(String str, int fontSize) {
ArrayList pointList = StoreHousePath.getPath(str, fontSize * 0.01f, 14);
initWithPointList(pointList);
}
public void initWithStringArray(int id) {
String[] points = getResources().getStringArray(id);
ArrayList pointList = new ArrayList();
for (int i = 0; i < points.length; i++) {
String[] x = points[i].split(",");
float[] f = new float[4];
for (int j = 0; j < 4; j++) {
f[j] = Float.parseFloat(x[j]);
}
pointList.add(f);
}
initWithPointList(pointList);
}
public float getScale() {
return mScale;
}
public void setScale(float scale) {
mScale = scale;
}
public void initWithPointList(ArrayList pointList) {
float drawWidth = 0;
float drawHeight = 0;
boolean shouldLayout = mItemList.size() > 0;
mItemList.clear();
for (int i = 0; i < pointList.size(); i++) {
float[] line = pointList.get(i);
PointF startPoint = new PointF(DensityUtil.dip2px(line[0]) * mScale, DensityUtil.dip2px(line[1]) * mScale);
PointF endPoint = new PointF(DensityUtil.dip2px(line[2]) * mScale, DensityUtil.dip2px(line[3]) * mScale);
drawWidth = Math.max(drawWidth, startPoint.x);
drawWidth = Math.max(drawWidth, endPoint.x);
drawHeight = Math.max(drawHeight, startPoint.y);
drawHeight = Math.max(drawHeight, endPoint.y);
StoreHouseBarItem item = new StoreHouseBarItem(i, startPoint, endPoint, mTextColor, mLineWidth);
item.resetPosition(mHorizontalRandomness);
mItemList.add(item);
}
mDrawZoneWidth = (int) Math.ceil(drawWidth);
mDrawZoneHeight = (int) Math.ceil(drawHeight);
if (shouldLayout) {
requestLayout();
}
}
private void beginLoading() {
mIsInLoading = true;
mAniController.start();
invalidate();
}
private void loadFinish() {
mIsInLoading = false;
mAniController.stop();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
float progress = mProgress;
int c1 = canvas.save();
int len = mItemList.size();
for (int i = 0; i < len; i++) {
canvas.save();
StoreHouseBarItem storeHouseBarItem = mItemList.get(i);
float offsetX = mOffsetX + storeHouseBarItem.midPoint.x;
float offsetY = mOffsetY + storeHouseBarItem.midPoint.y;
if (mIsInLoading) {
storeHouseBarItem.getTransformation(getDrawingTime(), mTransformation);
canvas.translate(offsetX, offsetY);
} else {
if (progress == 0) {
storeHouseBarItem.resetPosition(mHorizontalRandomness);
continue;
}
float startPadding = (1 - mInternalAnimationFactor) * i / len;
float endPadding = 1 - mInternalAnimationFactor - startPadding;
// done
if (progress == 1 || progress >= 1 - endPadding) {
canvas.translate(offsetX, offsetY);
storeHouseBarItem.setAlpha(mBarDarkAlpha);
} else {
float realProgress;
if (progress <= startPadding) {
realProgress = 0;
} else {
realProgress = Math.min(1, (progress - startPadding) / mInternalAnimationFactor);
}
offsetX += storeHouseBarItem.translationX * (1 - realProgress);
offsetY += -mDropHeight * (1 - realProgress);
Matrix matrix = new Matrix();
matrix.postRotate(360 * realProgress);
matrix.postScale(realProgress, realProgress);
matrix.postTranslate(offsetX, offsetY);
storeHouseBarItem.setAlpha(mBarDarkAlpha * realProgress);
canvas.concat(matrix);
}
}
storeHouseBarItem.draw(canvas);
canvas.restore();
}
if (mIsInLoading) {
invalidate();
}
canvas.restoreToCount(c1);
}
@Override
public long succeedRetention() {
return 200;
}
@Override
public long failingRetention() {
return 200;
}
@Override
public int refreshHeight() {
return getHeight();
}
@Override
public int maxOffsetHeight() {
return 2*getHeight();
}
@Override
public void onReset(@NotNull KRefreshLayout refreshLayout) {
loadFinish();
for (int i = 0; i < mItemList.size(); i++) {
mItemList.get(i).resetPosition(mHorizontalRandomness);
}
}
@Override
public void onPrepare(@NotNull KRefreshLayout refreshLayout) {
}
@Override
public void onRefresh(@NotNull KRefreshLayout refreshLayout) {
beginLoading();
}
@Override
public void onComplete(@NotNull KRefreshLayout refreshLayout, boolean isSuccess) {
loadFinish();
}
@Override
public void onScroll(@NotNull KRefreshLayout refreshLayout, int distance, float percent, boolean refreshing) {
float currentPercent = Math.min(1f, percent);
setProgress(currentPercent);
invalidate();
}
private class AniController implements Runnable {
private int mTick = 0;
private int mCountPerSeg = 0;
private int mSegCount = 0;
private int mInterval = 0;
private boolean mRunning = true;
private void start() {
mRunning = true;
mTick = 0;
mInterval = mLoadingAniDuration / mItemList.size();
mCountPerSeg = mLoadingAniSegDuration / mInterval;
mSegCount = mItemList.size() / mCountPerSeg + 1;
run();
}
@Override
public void run() {
int pos = mTick % mCountPerSeg;
for (int i = 0; i < mSegCount; i++) {
int index = i * mCountPerSeg + pos;
if (index > mTick) {
continue;
}
index = index % mItemList.size();
StoreHouseBarItem item = mItemList.get(index);
item.setFillAfter(false);
item.setFillEnabled(true);
item.setFillBefore(false);
item.setDuration(mLoadingAniItemDuration);
item.start(mFromAlpha, mToAlpha);
}
mTick++;
if (mRunning) {
postDelayed(this, mInterval);
}
}
private void stop() {
mRunning = false;
removeCallbacks(this);
}
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/header/storehouse/StoreHousePath.java
================================================
package gorden.krefreshlayout.demo.header.storehouse;
import android.util.SparseArray;
import java.util.ArrayList;
/**
* Created by srain on 11/7/14.
*/
public class StoreHousePath {
private static final SparseArray sPointList;
static {
sPointList = new SparseArray();
float[][] LETTERS = new float[][]{
new float[]{
// A
24, 0, 1, 22,
1, 22, 1, 72,
24, 0, 47, 22,
47, 22, 47, 72,
1, 48, 47, 48
},
new float[]{
// B
0, 0, 0, 72,
0, 0, 37, 0,
37, 0, 47, 11,
47, 11, 47, 26,
47, 26, 38, 36,
38, 36, 0, 36,
38, 36, 47, 46,
47, 46, 47, 61,
47, 61, 38, 71,
37, 72, 0, 72,
},
new float[]{
// C
47, 0, 0, 0,
0, 0, 0, 72,
0, 72, 47, 72,
},
new float[]{
// D
0, 0, 0, 72,
0, 0, 24, 0,
24, 0, 47, 22,
47, 22, 47, 48,
47, 48, 23, 72,
23, 72, 0, 72,
},
new float[]{
// E
0, 0, 0, 72,
0, 0, 47, 0,
0, 36, 37, 36,
0, 72, 47, 72,
},
new float[]{
// F
0, 0, 0, 72,
0, 0, 47, 0,
0, 36, 37, 36,
},
new float[]{
// G
47, 23, 47, 0,
47, 0, 0, 0,
0, 0, 0, 72,
0, 72, 47, 72,
47, 72, 47, 48,
47, 48, 24, 48,
},
new float[]{
// H
0, 0, 0, 72,
0, 36, 47, 36,
47, 0, 47, 72,
},
new float[]{
// I
0, 0, 47, 0,
24, 0, 24, 72,
0, 72, 47, 72,
},
new float[]{
// J
47, 0, 47, 72,
47, 72, 24, 72,
24, 72, 0, 48,
},
new float[]{
// K
0, 0, 0, 72,
47, 0, 3, 33,
3, 38, 47, 72,
},
new float[]{
// L
0, 0, 0, 72,
0, 72, 47, 72,
},
new float[]{
// M
0, 0, 0, 72,
0, 0, 24, 23,
24, 23, 47, 0,
47, 0, 47, 72,
},
new float[]{
// N
0, 0, 0, 72,
0, 0, 47, 72,
47, 72, 47, 0,
},
new float[]{
// O
0, 0, 0, 72,
0, 72, 47, 72,
47, 72, 47, 0,
47, 0, 0, 0,
},
new float[]{
// P
0, 0, 0, 72,
0, 0, 47, 0,
47, 0, 47, 36,
47, 36, 0, 36,
},
new float[]{
// Q
0, 0, 0, 72,
0, 72, 23, 72,
23, 72, 47, 48,
47, 48, 47, 0,
47, 0, 0, 0,
24, 28, 47, 71,
},
new float[]{
// R
0, 0, 0, 72,
0, 0, 47, 0,
47, 0, 47, 36,
47, 36, 0, 36,
0, 37, 47, 72,
},
new float[]{
// S
47, 0, 0, 0,
0, 0, 0, 36,
0, 36, 47, 36,
47, 36, 47, 72,
47, 72, 0, 72,
},
new float[]{
// T
0, 0, 47, 0,
24, 0, 24, 72,
},
new float[]{
// U
0, 0, 0, 72,
0, 72, 47, 72,
47, 72, 47, 0,
},
new float[]{
// V
0, 0, 24, 72,
24, 72, 47, 0,
},
new float[]{
// W
0, 0, 0, 72,
0, 72, 24, 49,
24, 49, 47, 72,
47, 72, 47, 0
},
new float[]{
// X
0, 0, 47, 72,
47, 0, 0, 72
},
new float[]{
// Y
0, 0, 24, 23,
47, 0, 24, 23,
24, 23, 24, 72
},
new float[]{
// Z
0, 0, 47, 0,
47, 0, 0, 72,
0, 72, 47, 72
},
};
final float[][] NUMBERS = new float[][]{
new float[]{
// 0
0, 0, 0, 72,
0, 72, 47, 72,
47, 72, 47, 0,
47, 0, 0, 0,
},
new float[]{
// 1
24, 0, 24, 72,
},
new float[]{
// 2
0, 0, 47, 0,
47, 0, 47, 36,
47, 36, 0, 36,
0, 36, 0, 72,
0, 72, 47, 72
},
new float[]{
// 3
0, 0, 47, 0,
47, 0, 47, 36,
47, 36, 0, 36,
47, 36, 47, 72,
47, 72, 0, 72,
},
new float[]{
// 4
0, 0, 0, 36,
0, 36, 47, 36,
47, 0, 47, 72,
},
new float[]{
// 5
0, 0, 0, 36,
0, 36, 47, 36,
47, 36, 47, 72,
47, 72, 0, 72,
0, 0, 47, 0
},
new float[]{
// 6
0, 0, 0, 72,
0, 72, 47, 72,
47, 72, 47, 36,
47, 36, 0, 36
},
new float[]{
// 7
0, 0, 47, 0,
47, 0, 47, 72
},
new float[]{
// 8
0, 0, 0, 72,
0, 72, 47, 72,
47, 72, 47, 0,
47, 0, 0, 0,
0, 36, 47, 36
},
new float[]{
// 9
47, 0, 0, 0,
0, 0, 0, 36,
0, 36, 47, 36,
47, 0, 47, 72,
}
};
// A - Z
for (int i = 0; i < LETTERS.length; i++) {
sPointList.append(i + 65, LETTERS[i]);
}
// a - z
for (int i = 0; i < LETTERS.length; i++) {
sPointList.append(i + 65 + 32, LETTERS[i]);
}
// 0 - 9
for (int i = 0; i < NUMBERS.length; i++) {
sPointList.append(i + 48, NUMBERS[i]);
}
// blank
addChar(' ', new float[]{});
// -
addChar('-', new float[]{
0, 36, 47, 36
});
// .
addChar('.', new float[]{
24, 60, 24, 72
});
}
public static void addChar(char c, float[] points) {
sPointList.append(c, points);
}
public static ArrayList getPath(String str) {
return getPath(str, 1, 14);
}
/**
* @param str
* @param scale
* @param gapBetweenLetter
* @return ArrayList of float[] {x1, y1, x2, y2}
*/
public static ArrayList getPath(String str, float scale, int gapBetweenLetter) {
ArrayList list = new ArrayList();
float offsetForWidth = 0;
for (int i = 0; i < str.length(); i++) {
int pos = str.charAt(i);
int key = sPointList.indexOfKey(pos);
if (key == -1) {
continue;
}
float[] points = sPointList.get(pos);
int pointCount = points.length / 4;
for (int j = 0; j < pointCount; j++) {
float[] line = new float[4];
for (int k = 0; k < 4; k++) {
float l = points[j * 4 + k];
// x
if (k % 2 == 0) {
line[k] = (l + offsetForWidth) * scale;
}
// y
else {
line[k] = l * scale;
}
}
list.add(line);
}
offsetForWidth += 57 + gapBetweenLetter;
}
return list;
}
}
================================================
FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/MainActivity.kt
================================================
package gorden.krefreshlayout.demo.ui
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.SimpleAdapter
import gorden.krefreshlayout.demo.R
import gorden.krefreshlayout.demo.util.XLog
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
val gridItems = arrayOf("RecyclerView", "ScrollView", "NestedScrollView", "ViewPager in NestedScrollView", "ViewPager in ScrollView"
, "WebView ", "Fragment in ViewPager", "CoordinatorLayout", "Wechat_Refresh","LoadMoreView","gordenxqw@gmail.com", "QQ:354419188")
val dataList = ArrayList