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)

![img](https://github.com/XiaoQiWen/Resources/raw/master/KRefreshLayout/gif6.gif)
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[![Version](https://img.shields.io/badge/version-1.3-brightgreen.svg)](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
![](https://github.com/XiaoQiWen/Resources/raw/master/KRefreshLayout/gif0.gif) ![](https://github.com/XiaoQiWen/Resources/raw/master/KRefreshLayout/gif1.gif)

![](https://github.com/XiaoQiWen/Resources/raw/master/KRefreshLayout/gif2.gif) ![](https://github.com/XiaoQiWen/Resources/raw/master/KRefreshLayout/gif3.gif)

![](https://github.com/XiaoQiWen/Resources/raw/master/KRefreshLayout/gif4.gif) ![](https://github.com/XiaoQiWen/Resources/raw/master/KRefreshLayout/gif5.gif) ## 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>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) for (item in gridItems) { val map = HashMap() map.put("name", item) dataList.add(map) } gridView.adapter = SimpleAdapter(this, dataList, R.layout.item_main, arrayOf("name"), intArrayOf(R.id.textView)) gridView.setOnItemClickListener { parent, view, position, id -> val intent = Intent("SampleActivity") intent.putExtra("position", position); intent.putExtra("name", gridItems[position]) startActivity(intent) } refreshLayout.setKRefreshListener { XLog.e("xxx","开始刷新") refreshLayout.postDelayed({ refreshLayout.refreshComplete(true) }, 2000) } // refreshLayout.startRefresh() } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/SampleActivity.kt ================================================ package gorden.krefreshlayout.demo.ui import android.app.Activity import android.content.Intent import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v4.app.FragmentManager import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.ui.fragment.* import kotlinx.android.synthetic.main.activity_sample.* class SampleActivity : AppCompatActivity() { private var mFragment:ISampleFragment? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_sample) text_title.text = intent.getStringExtra("name") btn_back.setOnClickListener { finish() } btn_setting.setOnClickListener { val intent = Intent("SettingActivity") intent.putExtra("header",mFragment?.headerPosition) intent.putExtra("pincontent",mFragment?.pinContent) intent.putExtra("keepheader",mFragment?.keepHeaderWhenRefresh) intent.putExtra("durationoffset",mFragment?.durationOffset) intent.putExtra("refreshtime",mFragment?.refreshTime) startActivityForResult(intent,612) } val position = intent.getIntExtra("position",0) val manager:FragmentManager = supportFragmentManager when(position){ 0-> mFragment = SampleAFragment() 1-> mFragment = SampleBFragment() 2-> mFragment = SampleCFragment() 3-> mFragment = SampleDFragment() 4-> mFragment = SampleEFragment() 5-> mFragment = SampleFFragment() 6-> mFragment = SampleGFragment() 7-> mFragment = SampleHFragment() 8-> mFragment = SampleIFragment() 9-> mFragment = SampleJFragment() else ->mFragment = SampleAFragment() } manager.beginTransaction().replace(R.id.frame_content,mFragment).commit() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 612&&resultCode == Activity.RESULT_OK){ mFragment?.headerPosition = data!!.getIntExtra("header",mFragment!!.headerPosition) mFragment?.pinContent = data.getBooleanExtra("pincontent",mFragment!!.pinContent) mFragment?.keepHeaderWhenRefresh = data.getBooleanExtra("keepheader",mFragment!!.keepHeaderWhenRefresh) mFragment?.durationOffset = data.getLongExtra("durationoffset",mFragment!!.durationOffset) mFragment?.refreshTime = data.getLongExtra("refreshtime",mFragment!!.refreshTime) } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/SettingActivity.kt ================================================ package gorden.krefreshlayout.demo.ui import android.app.Activity import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.text.TextUtils import android.view.View import android.widget.AdapterView import gorden.krefreshlayout.demo.R import kotlinx.android.synthetic.main.activity_setting.* class SettingActivity : AppCompatActivity() { private var headerPosition = 0 private var pinContent = false private var keepHeaderWhenRefresh = true private var durationOffset = 200L private var refreshTime = 2000L override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_setting) btn_back.setOnClickListener { finish() } headerPosition = intent.getIntExtra("header", 0) pinContent = intent.getBooleanExtra("pincontent", false) keepHeaderWhenRefresh = intent.getBooleanExtra("keepheader", true) durationOffset = intent.getLongExtra("durationoffset", 200) refreshTime = intent.getLongExtra("refreshtime", 2000) spinner.setSelection(headerPosition) togglePinContent.isChecked = pinContent toggleKeepHeader.isChecked = keepHeaderWhenRefresh edit_offset.setText(durationOffset.toString()) edit_refresh.setText(refreshTime.toString()) spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onNothingSelected(parent: AdapterView<*>?) { } override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { headerPosition = position } } } override fun finish() { val data = Intent() pinContent = togglePinContent.isChecked keepHeaderWhenRefresh = toggleKeepHeader.isChecked durationOffset = if (TextUtils.isEmpty(edit_offset.text)) 200 else edit_offset.text.toString().toLong() refreshTime = if (TextUtils.isEmpty(edit_refresh.text)) 2000 else edit_refresh.text.toString().toLong() data.putExtra("header", headerPosition) data.putExtra("pincontent", pinContent) data.putExtra("keepheader", keepHeaderWhenRefresh) data.putExtra("durationoffset", durationOffset) data.putExtra("refreshtime", refreshTime) setResult(Activity.RESULT_OK, data) super.finish() } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/ISampleFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.support.v4.app.Fragment import android.view.ViewGroup import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.krefreshlayout.demo.header.WechatHeader import gorden.krefreshlayout.demo.header.circle.CircleHeader import gorden.krefreshlayout.demo.header.fungame.FunGameHeader import gorden.krefreshlayout.demo.header.materia.MateriaProgressHeader import gorden.krefreshlayout.demo.header.rentals.RentalsSunHeaderView import gorden.krefreshlayout.demo.header.storehouse.StoreHouseHeader import gorden.krefreshlayout.demo.util.DensityUtil import gorden.refresh.KRefreshLayout /** * document * Created by Gordn on 2017/6/21. */ abstract class ISampleFragment : Fragment() { abstract val mRefreshLayout: KRefreshLayout var headerPosition: Int = 0 get() = field set(value) { if (field != value) { field = value header() } } var pinContent: Boolean = false get() = field set(value) { if (field != value) { field = value mRefreshLayout.pinContent = field } } var keepHeaderWhenRefresh: Boolean = true get() = field set(value) { if (field != value) { field = value mRefreshLayout.keepHeaderWhenRefresh = field } } var durationOffset: Long = 200 get() = field set(value) { if (field != value) { field = value mRefreshLayout.durationOffset=field } } var refreshTime: Long = 2000 get() = field set(value) { field = value } fun header(): Unit { when (headerPosition) { 0 -> mRefreshLayout.setHeader(ClassicalHeader(context)) 1 -> mRefreshLayout.setHeader(MateriaProgressHeader(context), ViewGroup.LayoutParams.MATCH_PARENT, DensityUtil.dip2px(80)) 2 -> mRefreshLayout.setHeader(RentalsSunHeaderView(context)) 3 -> mRefreshLayout.setHeader(StoreHouseHeader(context)) 4 -> mRefreshLayout.setHeader(CircleHeader(context)) 5 -> mRefreshLayout.setHeader(FunGameHeader(context)) 6 -> mRefreshLayout.setHeader(WechatHeader(context)) else -> mRefreshLayout.removeHeader() } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleAFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.support.v7.widget.DividerItemDecoration import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_recyclerview.* /** * document * Created by Gordn on 2017/6/21. */ class SampleAFragment : ISampleFragment() { override val mRefreshLayout: KRefreshLayout get() = refreshLayout override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_recyclerview,container,false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setHeader(ClassicalHeader(context)) recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.addItemDecoration(DividerItemDecoration(context,DividerItemDecoration.VERTICAL)) recyclerView.adapter = object : RecyclerView.Adapter() { override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) { } override fun getItemCount(): Int { return 20 } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder { return object :RecyclerView.ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_sample,parent,false)){} } } refreshLayout.setKRefreshListener { refreshLayout.postDelayed({ refreshLayout?.refreshComplete(true) },refreshTime) } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleBFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_scrollview.* /** * document * Created by Gordn on 2017/6/21. */ class SampleBFragment : ISampleFragment() { override val mRefreshLayout: KRefreshLayout get() = refreshLayout override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_scrollview,container,false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setHeader(ClassicalHeader(context)) refreshLayout.setKRefreshListener { refreshLayout.postDelayed({ refreshLayout?.refreshComplete(true) },refreshTime) } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleCFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_nestedscrollview.* /** * document * Created by Gordn on 2017/6/21. */ class SampleCFragment : ISampleFragment() { override val mRefreshLayout: KRefreshLayout get() = refreshLayout override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_nestedscrollview,container,false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setHeader(ClassicalHeader(context)) refreshLayout.setKRefreshListener { refreshLayout.postDelayed({ refreshLayout?.refreshComplete(true) },refreshTime) } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleDFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_vp_nestedscrollview.* /** * document * Created by Gordn on 2017/6/21. */ class SampleDFragment : ISampleFragment() { override val mRefreshLayout: KRefreshLayout get() = refreshLayout override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_vp_nestedscrollview, container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setHeader(ClassicalHeader(context)) viewPager.setImages(arrayListOf(R.drawable.img_pager1, R.drawable.img_pager2, R.drawable.img_pager3)) refreshLayout.setKRefreshListener { refreshLayout.postDelayed({ refreshLayout?.refreshComplete(true) }, refreshTime) } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleEFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_vp_scrollview.* /** * document * Created by Gordn on 2017/6/21. */ class SampleEFragment : ISampleFragment() { override val mRefreshLayout: KRefreshLayout get() = refreshLayout override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_vp_scrollview, container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setHeader(ClassicalHeader(context)) viewPager.setImages(arrayListOf(R.drawable.img_pager1, R.drawable.img_pager2, R.drawable.img_pager3)) refreshLayout.setKRefreshListener { refreshLayout.postDelayed({ refreshLayout?.refreshComplete(true) }, refreshTime) } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleFFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_webview.* /** * document * Created by Gordn on 2017/6/21. */ class SampleFFragment : ISampleFragment() { override val mRefreshLayout: KRefreshLayout get() = refreshLayout override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_webview, container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setHeader(ClassicalHeader(context)) webView.loadUrl("http://www.baidu.com") webView.setWebViewClient(object :WebViewClient(){ override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { return true } }) refreshLayout.setKRefreshListener { refreshLayout.postDelayed({ refreshLayout?.refreshComplete(true) }, refreshTime) } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleGFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.support.v4.app.Fragment import android.support.v4.app.FragmentStatePagerAdapter import android.support.v4.view.ViewPager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_viewpager.* /** * document * Created by Gordn on 2017/6/21. */ class SampleGFragment : ISampleFragment() { var currentPosition = 0 val fragmentList = arrayListOf(SampleAFragment(), SampleDFragment(), SampleCFragment()) val titleList = arrayListOf("TAB1","TAB2","TAB3") override val mRefreshLayout: KRefreshLayout get() = fragmentList[currentPosition].mRefreshLayout override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_viewpager, container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) tabLayout.setupWithViewPager(viewPager) viewPager.adapter = object : FragmentStatePagerAdapter(fragmentManager) { override fun getItem(position: Int): Fragment { return fragmentList[position] } override fun getCount(): Int { return 3 } override fun getPageTitle(position: Int): CharSequence { return titleList[position] } } viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { } override fun onPageSelected(position: Int) { currentPosition = position } override fun onPageScrollStateChanged(state: Int) { } }) } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleHFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.support.v7.widget.DividerItemDecoration import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_coordinatorlayout.* /** * document * Created by Gordn on 2017/6/21. */ class SampleHFragment : ISampleFragment() { override val mRefreshLayout: KRefreshLayout get() = refreshLayout override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_coordinatorlayout, container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setHeader(ClassicalHeader(context)) recyclerView.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) textView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> refreshLayout.refreshEnable = scrolling_header.translationY==0f } recyclerView.adapter = object : RecyclerView.Adapter() { override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) { } override fun getItemCount(): Int { return 20 } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder { return object : RecyclerView.ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_sample,parent,false)){} } } refreshLayout.setKRefreshListener { refreshLayout.postDelayed({ refreshLayout?.refreshComplete(true) },refreshTime) } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleIFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.krefreshlayout.demo.header.WechatHeader import gorden.krefreshlayout.demo.util.DensityUtil import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_vp_nestedscrollview.* /** * document * Created by Gordn on 2017/6/21. */ class SampleIFragment : ISampleFragment() { override val mRefreshLayout: KRefreshLayout get() = refreshLayout override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_vp_nestedscrollview, container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setHeader(WechatHeader(context)) refreshLayout.keepHeaderWhenRefresh = false viewPager.setImages(arrayListOf(R.drawable.img_pager1, R.drawable.img_pager2, R.drawable.img_pager3)) refreshLayout.setKRefreshListener { refreshLayout.postDelayed({ refreshLayout?.refreshComplete(true) }, refreshTime) } //头部自带偏移效果的header 自动刷新 refreshLayout.headerOffset = DensityUtil.dip2px(50) (refreshLayout.getHeader() as WechatHeader).mDistance = DensityUtil.dip2px(50) refreshLayout.startRefresh() } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/ui/fragment/SampleJFragment.kt ================================================ package gorden.krefreshlayout.demo.ui.fragment import android.os.Bundle import android.support.v7.widget.DividerItemDecoration import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import gorden.krefreshlayout.demo.R import gorden.krefreshlayout.demo.footer.ClassicalFooter import gorden.krefreshlayout.demo.header.ClassicalHeader import gorden.refresh.KRefreshLayout import kotlinx.android.synthetic.main.layout_krecyclerview.* /** * document * Created by Gordn on 2017/6/21. */ class SampleJFragment : ISampleFragment() { override val mRefreshLayout: KRefreshLayout get() = refreshLayout var count = 0 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.layout_krecyclerview, container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setHeader(ClassicalHeader(context)) recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) val adapter = object : RecyclerView.Adapter() { override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) { } override fun getItemCount(): Int { return count } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder { return object : RecyclerView.ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_sample, parent, false)) {} } } recyclerView.adapter = adapter recyclerView.setLoadMoreView(ClassicalFooter(context)) refreshLayout.setKRefreshListener { refreshLayout.postDelayed({ count = 10 adapter.notifyDataSetChanged() recyclerView.hasMore = true refreshLayout?.refreshComplete(true) }, refreshTime) } refreshLayout.startRefresh() recyclerView.setLoadMoreListener { recyclerView.postDelayed({ count += 10 adapter.notifyDataSetChanged() recyclerView.loadMoreComplete(count <= 20) }, 2000) } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/util/CrashHandler.java ================================================ package gorden.krefreshlayout.demo.util; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Environment; import android.os.Process; import android.util.Log; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.text.SimpleDateFormat; import java.util.Date; /** * 错误日志 * Created by Gorden on 2015/11/23. */ @SuppressWarnings("unused") public class CrashHandler implements Thread.UncaughtExceptionHandler { private static final String TAG = "crash_log"; private Context mContext; private static final String SDCARD_ROOT = Environment.getExternalStorageDirectory().toString(); private static final String PATH = Environment.getExternalStorageDirectory().getPath() + File.separator + "Acrash"; private static final String FILE_NAME = "crash"; //log文件的后缀名 private static final String FILE_NAME_SUFFIX = ".log"; private static CrashHandler mInstance = new CrashHandler(); //系统默认的异常处理(默认情况下,系统会终止当前的异常程序) private Thread.UncaughtExceptionHandler mDefaultCrashHandler; private boolean DEBUG = false; private CrashHandler() { } public static CrashHandler getInstance() { return mInstance; } @Override public void uncaughtException(Thread thread, Throwable ex) { // try { // //导出异常信息到SD卡中 // saveExceptionToSDCard(ex); // //这里可以通过网络上传异常信息到服务器,便于开发人员分析日志从而解决bug // uploadExceptionToServer(ex); // } catch (IOException e) { // e.printStackTrace(); // } //打印出当前调用栈信息 if (DEBUG) { XLog.e(TAG, "开始打印错误日志"); StringWriter mStringWriter = new StringWriter(); PrintWriter mPrintWriter = new PrintWriter(mStringWriter); ex.printStackTrace(mPrintWriter); mPrintWriter.close(); XLog.e(TAG, mStringWriter.toString()); } //如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己 if (mDefaultCrashHandler != null) { mDefaultCrashHandler.uncaughtException(thread, ex); } else { // ActivityStack.getInstance().finishAllActivity(); Process.killProcess(Process.myPid()); } } /** * 为我们的应用程序设置自定义Crash处理 */ public void init(Context context, boolean isDebug) { mContext = context.getApplicationContext(); DEBUG = isDebug; //获取系统默认的异常处理器 mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler(); //将当前实例设为系统默认的异常处理器 Thread.setDefaultUncaughtExceptionHandler(this); } private void saveExceptionToSDCard(Throwable ex) throws IOException { if (!DEBUG) return; //如果SD卡不存在或无法使用,则无法把异常信息写入SD卡 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { Log.w(TAG, "sdcard unmounted,skip dump exception"); return; } File dir = new File(PATH); if (!dir.exists()) { dir.mkdirs(); } long current = System.currentTimeMillis(); String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current)); //以当前时间创建log文件 File file = new File(PATH + File.separator + FILE_NAME + time + FILE_NAME_SUFFIX); try { PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file))); //导出发生异常的时间 pw.println(time); //导出手机信息 dumpPhoneInfo(pw); pw.println(); //导出异常的调用栈信息 ex.printStackTrace(pw); pw.close(); } catch (Exception e) { Log.e(TAG, "dump crash info failed"); } } private void uploadExceptionToServer(Throwable ex) { //TODO Upload Exception Message To Your Web Server } private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException { //应用的版本名称和版本号 PackageManager pm = mContext.getPackageManager(); PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); pw.print("App Version: "); pw.print(pi.versionName); pw.print('_'); pw.println(pi.versionCode); //android版本号 pw.print("OS Version: "); pw.print(Build.VERSION.RELEASE); pw.print("_"); pw.println(Build.VERSION.SDK_INT); //手机制造商 pw.print("Vendor: "); pw.println(Build.MANUFACTURER); //手机型号 pw.print("Model: "); pw.println(Build.MODEL); //cpu架构 pw.print("CPU ABI: "); pw.println(Build.CPU_ABI); } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/util/DensityUtil.java ================================================ package gorden.krefreshlayout.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; } public static int appHeight(){ return Resources.getSystem().getDisplayMetrics().heightPixels; } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/util/XLog.java ================================================ package gorden.krefreshlayout.demo.util; import android.text.TextUtils; import android.util.Log; import java.io.PrintWriter; import java.io.StringWriter; /** * Log日志打印工具类 * Created by gorden on 2016/7/26. */ public class XLog { public static final String NULL_TIPS = "Log with null object"; private static final String DEFAULT_MESSAGE = "execute"; private static final String PARAM = "Param"; private static final String NULL = "null"; private static final String SUFFIX = ".java"; private static final String TAG_DEFAULT = "XLog"; private static boolean IS_SHOW_LOG = true; private static String TAG_NAME; private static boolean mIsGlobalTagEmpty = true; public static final int V = 0x1; public static final int D = 0x2; public static final int I = 0x3; public static final int W = 0x4; public static final int E = 0x5; public static final int A = 0x6; private static final int STACK_TRACE_INDEX = 5; public static void init(boolean showLog){ IS_SHOW_LOG = showLog; } public static void init(boolean showLog,String tag){ IS_SHOW_LOG = showLog; TAG_NAME = tag; mIsGlobalTagEmpty = TextUtils.isEmpty(TAG_NAME); } public static void v() { printLog(V, null, DEFAULT_MESSAGE); } public static void v(Object msg) { printLog(V, null, msg); } public static void v(String tag, Object... objects) { printLog(V, tag, objects); } public static void d() { printLog(D, null, DEFAULT_MESSAGE); } public static void d(Object msg) { printLog(D, null, msg); } public static void d(String tag, Object... objects) { printLog(D, tag, objects); } public static void i() { printLog(I, null, DEFAULT_MESSAGE); } public static void i(Object msg) { printLog(I, null, msg); } public static void i(String tag, Object... objects) { printLog(I, tag, objects); } public static void w() { printLog(W, null, DEFAULT_MESSAGE); } public static void w(Object msg) { printLog(W, null, msg); } public static void w(String tag, Object... objects) { printLog(W, tag, objects); } public static void e() { printLog(E, null, DEFAULT_MESSAGE); } public static void e(Object msg) { printLog(E, null, msg); } public static void e(String tag, Object... objects) { printLog(E, tag, objects); } public static void exception(Exception ex){ StringWriter mStringWriter = new StringWriter(); PrintWriter mPrintWriter = new PrintWriter(mStringWriter); ex.printStackTrace(mPrintWriter); mPrintWriter.close(); printLog(E,null,mStringWriter.toString()); } public static void a() { printLog(A, null, DEFAULT_MESSAGE); } public static void a(Object msg) { printLog(A, null, msg); } public static void a(String tag, Object... objects) { printLog(A, tag, objects); } private static void printLog(int type,String tagStr,Object... objects) { if(!IS_SHOW_LOG) return; String[] contents = wrapperContent(tagStr, objects); String tag = contents[0]; String msg = contents[1]; String headString = contents[2]; switch (type) { case V: case D: case I: case W: case E: case A: printDefault(type, tag, headString + msg); break; } } public static void printDefault(int type, String tag, String msg) { int index = 0; int maxLength = 4000; int countOfSub = msg.length() / maxLength; if (countOfSub > 0) { for (int i = 0; i < countOfSub; i++) { String sub = msg.substring(index, index + maxLength); printSub(type, tag, sub); index += maxLength; } printSub(type, tag, msg.substring(index, msg.length())); } else { printSub(type, tag, msg); } } private static void printSub(int type, String tag, String sub) { switch (type) { case V: Log.v(tag, sub); break; case D: Log.d(tag, sub); break; case I: Log.i(tag, sub); break; case W: Log.w(tag, sub); break; case E: Log.e(tag, sub); break; case A: Log.wtf(tag, sub); break; } } private static String[] wrapperContent(String tagStr, Object... objects) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); StackTraceElement targetElement = stackTrace[STACK_TRACE_INDEX]; String className = targetElement.getClassName(); String[] classNameInfo = className.split("\\."); if (classNameInfo.length > 0) { className = classNameInfo[classNameInfo.length - 1] + SUFFIX; } if (className.contains("$")) { className = className.split("\\$")[0] + SUFFIX; } String methodName = targetElement.getMethodName(); int lineNumber = targetElement.getLineNumber(); if (lineNumber < 0) { lineNumber = 0; } String methodNameShort = methodName.substring(0, 1).toUpperCase() + methodName.substring(1); String tag = (tagStr == null ? className : tagStr); if (mIsGlobalTagEmpty && TextUtils.isEmpty(tag)) { tag = TAG_DEFAULT; } else if (!mIsGlobalTagEmpty && TextUtils.isEmpty(tagStr)) { tag = TAG_NAME; } String msg = (objects == null) ? NULL_TIPS : getObjectsString(objects); String headString = "[ (" + className + ":" + lineNumber + ")#" + methodNameShort + " ] "; return new String[]{tag, msg, headString}; } private static String getObjectsString(Object... objects) { if (objects.length > 1) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("\n"); for (int i = 0; i < objects.length; i++) { Object object = objects[i]; if (object == null) { stringBuilder.append(PARAM).append("[").append(i).append("]").append(" = ").append(NULL).append("\n"); } else { stringBuilder.append(PARAM).append("[").append(i).append("]").append(" = ").append(object.toString()).append("\n"); } } return stringBuilder.toString(); } else { Object object = objects[0]; return object == null ? NULL : object.toString(); } } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/widget/AdViewPager.java ================================================ package gorden.krefreshlayout.demo.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; import android.os.Message; import android.support.annotation.NonNull; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; //import com.bumptech.glide.Glide; import java.util.List; import gorden.krefreshlayout.demo.R; /** * 广告轮播ViewPager */ @SuppressWarnings("unused") public class AdViewPager extends RelativeLayout { private static final int RMP = LayoutParams.MATCH_PARENT; private static final int RWP = LayoutParams.WRAP_CONTENT; private static final int LWC = LinearLayout.LayoutParams.WRAP_CONTENT; private static final int WAIT_AUTO_PLAY = 1000; //Point位置 public static final int CENTER = 0; public static final int LEFT = 1; public static final int RIGHT = 2; private RelativeLayout mPointContainerRl; private LinearLayout mPointRealContainerLl; private ViewPager mViewPager; //本地图片资源 private List mImages; //网络图片资源 private List mImageUrls; //是否是网络图片 private boolean mIsImageUrl = false; //是否只有一张图片 private boolean mIsOneImg = false; //是否可以自动播放 private boolean mAutoPlayAble = true; //是否正在播放 private boolean mIsAutoPlaying = false; //自动播放时间 private int mAutoPalyTime = 5000; //当前页面位置 private int mCurrentPositon; //指示点位置 private int mPointPosition = CENTER; //指示点资源 private int mPointDrawableResId = R.drawable.selector_adpager_point; //指示容器背景 private Drawable mPointContainerBackgroundDrawable; //指示容器布局规则 private LayoutParams mPointRealContainerLp; //指示点是否可见 private boolean mPointsIsVisible = true; /** * 自动轮播handler */ private Handler mAutoPlayHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { mCurrentPositon++; mViewPager.setCurrentItem(mCurrentPositon); mAutoPlayHandler.sendEmptyMessageDelayed(WAIT_AUTO_PLAY, mAutoPalyTime); return false; } }); public AdViewPager(Context context) { this(context, null); } public AdViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } private void init(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AdViewPager); mPointsIsVisible = a.getBoolean(R.styleable.AdViewPager_ad_points_visibility, true); mPointPosition = a.getInt(R.styleable.AdViewPager_ad_points_position, CENTER); mPointContainerBackgroundDrawable = a.getDrawable(R.styleable.AdViewPager_ad_points_container_background); a.recycle(); setLayout(context); } private void setLayout(Context context) { //关闭view的OverScroll setOverScrollMode(OVER_SCROLL_NEVER); //设置指示器背景 if (mPointContainerBackgroundDrawable == null) { mPointContainerBackgroundDrawable = new ColorDrawable(Color.parseColor("#33AAAAAA")); } //添加ViewPager mViewPager = new ViewPager(context); addView(mViewPager, new LayoutParams(RMP, RMP)); //设置指示器背景容器 mPointContainerRl = new RelativeLayout(context); if (Build.VERSION.SDK_INT >= 16) { mPointContainerRl.setBackground(mPointContainerBackgroundDrawable); } else { mPointContainerRl.setBackgroundDrawable(mPointContainerBackgroundDrawable); } //设置内边距 mPointContainerRl.setPadding(0, 10, 0, 10); //设定指示器容器布局及位置 LayoutParams pointContainerLp = new LayoutParams(RMP, RWP); pointContainerLp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); addView(mPointContainerRl, pointContainerLp); //设置指示器容器 mPointRealContainerLl = new LinearLayout(context); mPointRealContainerLl.setOrientation(LinearLayout.HORIZONTAL); mPointRealContainerLp = new LayoutParams(RWP, RWP); mPointContainerRl.addView(mPointRealContainerLl, mPointRealContainerLp); mPointContainerRl.setVisibility(GONE); //设置指示器布局位置 if (mPointPosition == CENTER) { mPointRealContainerLp.addRule(RelativeLayout.CENTER_HORIZONTAL); } else if (mPointPosition == LEFT) { mPointRealContainerLp.addRule(RelativeLayout.ALIGN_PARENT_LEFT); } else if (mPointPosition == RIGHT) { mPointRealContainerLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); } } /** * 设置本地图片 */ public void setImages(List images) { //加载本地图片 mIsImageUrl = false; mIsOneImg = false; this.mImages = images; if (images.size() <= 1) mIsOneImg = true; //初始化ViewPager initViewPager(); } /** * 设置网络图片 */ public void setImagesUrl(List urls) { //加载网络图片 mIsImageUrl = true; mIsOneImg = false; this.mImageUrls = urls; if (urls.size() <= 1) mIsOneImg = true; //初始化ViewPager initViewPager(); } /** * 设置指示点是否可见 */ public void setPointsIsVisible(boolean isVisible) { if (mPointContainerRl != null) { if (isVisible) { mPointContainerRl.setVisibility(View.VISIBLE); } else { mPointContainerRl.setVisibility(View.GONE); } } } /** * 对应三个位置 CENTER,RIGHT,LEFT */ public void setPoinstPosition(int position) { //设置指示器布局位置 if (position == CENTER) { mPointRealContainerLp.addRule(RelativeLayout.CENTER_HORIZONTAL); } else if (position == LEFT) { mPointRealContainerLp.addRule(RelativeLayout.ALIGN_PARENT_LEFT); } else if (position == RIGHT) { mPointRealContainerLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); } } private void initViewPager() { //当图片多于1张时添加指示点 mPointRealContainerLl.removeAllViews(); mPointContainerRl.setVisibility(GONE); if (!mIsOneImg) { addPoints(); } //设置ViewPager AdPageAdapter adapter = new AdPageAdapter(); mViewPager.setAdapter(adapter); mViewPager.addOnPageChangeListener(mOnPageChangeListener); //跳转到首页 mViewPager.setCurrentItem(1, false); //当图片多于1张时开始轮播 if (!mIsOneImg) { startAutoPlay(); } else { stopAutoPlay(); } } /** * 返回真实的位置 */ private int toRealPosition(int position) { int realPosition; if (mIsImageUrl) { realPosition = (position - 1) % mImageUrls.size(); if (realPosition < 0) realPosition += mImageUrls.size(); } else { realPosition = (position - 1) % mImages.size(); if (realPosition < 0) realPosition += mImages.size(); } return realPosition; } private ViewPager.OnPageChangeListener mOnPageChangeListener = new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { if (mIsImageUrl) { mCurrentPositon = position % (mImageUrls.size() + 2); } else { mCurrentPositon = position % (mImages.size() + 2); } switchToPoint(toRealPosition(mCurrentPositon)); } @Override public void onPageScrollStateChanged(int state) { int current = mViewPager.getCurrentItem(); int lastReal = mViewPager.getAdapter().getCount() - 2; if (state == ViewPager.SCROLL_STATE_IDLE) { if (current == 0) { mViewPager.setCurrentItem(lastReal, false); } else if (current == lastReal + 1) { mViewPager.setCurrentItem(1, false); } } else if (state == ViewPager.SCROLL_STATE_DRAGGING) { if (current == lastReal + 1) { mViewPager.setCurrentItem(1, false); } else if (current == 0) { mViewPager.setCurrentItem(lastReal, false); } } } }; private class AdPageAdapter extends PagerAdapter { @Override public int getCount() { //当只有一张图片时返回1 if (mIsOneImg) { return 1; } //当为网络图片,返回网页图片长度 if (mIsImageUrl) return mImageUrls.size() + 2; //当为本地图片,返回本地图片长度 return mImages.size() + 2; } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Object instantiateItem(ViewGroup container, final int position) { ImageView imageView = new ImageView(getContext()); imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (mOnItemClickListener != null) { mOnItemClickListener.onItemClick(toRealPosition(position)); } } }); imageView.setScaleType(ImageView.ScaleType.FIT_XY); if (mIsImageUrl) { // Glide.with(getContext()).load(mImageUrls.get(toRealPosition(position))).asBitmap().into(imageView); } else { imageView.setImageResource(mImages.get(toRealPosition(position))); } container.addView(imageView); return imageView; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } } /** * 添加指示点 */ private void addPoints() { //设置指示器容器是否可见 if (mPointsIsVisible) { mPointContainerRl.setVisibility(View.VISIBLE); } LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LWC, LWC); lp.setMargins(10, 10, 10, 10); ImageView imageView; int length = mIsImageUrl ? mImageUrls.size() : mImages.size(); for (int i = 0; i < length; i++) { imageView = new ImageView(getContext()); imageView.setLayoutParams(lp); imageView.setImageResource(mPointDrawableResId); mPointRealContainerLl.addView(imageView); } switchToPoint(0); } /** * 切换指示器 */ private void switchToPoint(final int currentPoint) { for (int i = 0; i < mPointRealContainerLl.getChildCount(); i++) { mPointRealContainerLl.getChildAt(i).setEnabled(false); } mPointRealContainerLl.getChildAt(currentPoint).setEnabled(true); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mAutoPlayAble && !mIsOneImg) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: stopAutoPlay(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_OUTSIDE: startAutoPlay(); break; } } return super.dispatchTouchEvent(ev); } /** * 开始播放 */ public void startAutoPlay() { if (mAutoPlayAble && !mIsAutoPlaying) { mIsAutoPlaying = true; mAutoPlayHandler.sendEmptyMessageDelayed(WAIT_AUTO_PLAY, mAutoPalyTime); } } /** * 停止播放 */ public void stopAutoPlay() { if (mAutoPlayAble && mIsAutoPlaying) { mIsAutoPlaying = false; mAutoPlayHandler.removeMessages(WAIT_AUTO_PLAY); } } private OnItemClickListener mOnItemClickListener; public void setOnItemClickListener(OnItemClickListener listener) { this.mOnItemClickListener = listener; } public interface OnItemClickListener { void onItemClick(int position); } @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (visibility == View.VISIBLE) { if (mIsImageUrl && mImageUrls != null && mImageUrls.size() > 1) { startAutoPlay(); } else if (!mIsImageUrl && mImages != null && mImages.size() > 1) { startAutoPlay(); } } else { stopAutoPlay(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mAutoPlayHandler.removeCallbacksAndMessages(null); } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/widget/HeaderFloatBehavior.java ================================================ package gorden.krefreshlayout.demo.widget; import android.animation.ArgbEvaluator; import android.content.Context; import android.content.res.Resources; import android.support.design.widget.CoordinatorLayout; import android.util.AttributeSet; import android.view.View; import java.lang.ref.WeakReference; import gorden.krefreshlayout.demo.R; /** * Created by cyandev on 2016/12/14. */ public class HeaderFloatBehavior extends CoordinatorLayout.Behavior { private WeakReference dependentView; private ArgbEvaluator argbEvaluator; public HeaderFloatBehavior(Context context, AttributeSet attrs) { super(context, attrs); argbEvaluator = new ArgbEvaluator(); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { if (dependency != null && dependency.getId() == R.id.scrolling_header) { dependentView = new WeakReference<>(dependency); return true; } return false; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { Resources resources = getDependentView().getResources(); final float progress = 1.f - Math.abs(dependency.getTranslationY() / (dependency.getHeight() - resources.getDimension(R.dimen.collapsed_header_height))); // Translation final float collapsedOffset = resources.getDimension(R.dimen.collapsed_float_offset_y); final float initOffset = resources.getDimension(R.dimen.init_float_offset_y); final float translateY = collapsedOffset + (initOffset - collapsedOffset) * progress; child.setTranslationY(translateY); // Background child.setBackgroundColor((int) argbEvaluator.evaluate( progress, resources.getColor(R.color.colorCollapsedFloatBackground), resources.getColor(R.color.colorInitFloatBackground))); // Margins final float collapsedMargin = resources.getDimension(R.dimen.collapsed_float_margin); final float initMargin = resources.getDimension(R.dimen.init_float_margin); final int margin = (int) (collapsedMargin + (initMargin - collapsedMargin) * progress); CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); lp.setMargins(margin, 0, margin, 0); child.setLayoutParams(lp); return true; } private View getDependentView() { return dependentView.get(); } } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/widget/HeaderScrollingBehavior.java ================================================ package gorden.krefreshlayout.demo.widget; import android.content.Context; import android.content.res.Resources; import android.os.Handler; import android.support.design.widget.CoordinatorLayout; import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.View; import android.widget.Scroller; import java.lang.ref.WeakReference; import gorden.krefreshlayout.demo.R; public class HeaderScrollingBehavior extends CoordinatorLayout.Behavior { private boolean isExpanded = false; private boolean isScrolling = false; private WeakReference dependentView; private Scroller scroller; private Handler handler; public HeaderScrollingBehavior(Context context, AttributeSet attrs) { super(context, attrs); scroller = new Scroller(context); handler = new Handler(); } public boolean isExpanded() { return isExpanded; } @Override public boolean layoutDependsOn(CoordinatorLayout parent, RecyclerView child, View dependency) { if (dependency != null && dependency.getId() == R.id.scrolling_header) { dependentView = new WeakReference<>(dependency); return true; } return false; } @Override public boolean onLayoutChild(CoordinatorLayout parent, RecyclerView child, int layoutDirection) { CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); if (lp.height == CoordinatorLayout.LayoutParams.MATCH_PARENT) { child.layout(0, 0, parent.getWidth(), (int) (parent.getHeight() - getDependentViewCollapsedHeight())); return true; } return super.onLayoutChild(parent, child, layoutDirection); } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, RecyclerView child, View dependency) { Resources resources = getDependentView().getResources(); final float progress = 1.f - Math.abs(dependency.getTranslationY() / (dependency.getHeight() - resources.getDimension(R.dimen.collapsed_header_height))); child.setTranslationY(dependency.getHeight() + dependency.getTranslationY()); float scale = 1 + 0.4f * (1.f - progress); dependency.setScaleX(scale); dependency.setScaleY(scale); dependency.setAlpha(progress); return true; } @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View directTargetChild, View target, int nestedScrollAxes) { return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; } @Override public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, RecyclerView child, View directTargetChild, View target, int nestedScrollAxes) { scroller.abortAnimation(); isScrolling = false; super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); } @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View target, int dx, int dy, int[] consumed) { if (dy < 0) { return; } View dependentView = getDependentView(); float newTranslateY = dependentView.getTranslationY() - dy; float minHeaderTranslate = -(dependentView.getHeight() - getDependentViewCollapsedHeight()); if (newTranslateY > minHeaderTranslate) { dependentView.setTranslationY(newTranslateY); consumed[1] = dy; } } @Override public void onNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { if (dyUnconsumed > 0) { return; } View dependentView = getDependentView(); float newTranslateY = dependentView.getTranslationY() - dyUnconsumed; final float maxHeaderTranslate = 0; if (newTranslateY < maxHeaderTranslate) { dependentView.setTranslationY(newTranslateY); } } @Override public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, RecyclerView child, View target, float velocityX, float velocityY) { return onUserStopDragging(velocityY); } @Override public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View target) { if (!isScrolling) { onUserStopDragging(800); } } private boolean onUserStopDragging(float velocity) { View dependentView = getDependentView(); float translateY = dependentView.getTranslationY(); float minHeaderTranslate = -(dependentView.getHeight() - getDependentViewCollapsedHeight()); if (translateY == 0 || translateY == minHeaderTranslate) { return false; } boolean targetState; // Flag indicates whether to expand the content. if (Math.abs(velocity) <= 800) { if (Math.abs(translateY) < Math.abs(translateY - minHeaderTranslate)) { targetState = false; } else { targetState = true; } velocity = 800; // Limit velocity's minimum value. } else { if (velocity > 0) { targetState = true; } else { targetState = false; } } float targetTranslateY = targetState ? minHeaderTranslate : 0; scroller.startScroll(0, (int) translateY, 0, (int) (targetTranslateY - translateY), (int) (1000000 / Math.abs(velocity))); handler.post(flingRunnable); isScrolling = true; return true; } private float getDependentViewCollapsedHeight() { return getDependentView().getResources().getDimension(R.dimen.collapsed_header_height); } private View getDependentView() { return dependentView.get(); } private Runnable flingRunnable = new Runnable() { @Override public void run() { if (scroller.computeScrollOffset()) { getDependentView().setTranslationY(scroller.getCurrY()); handler.post(this); } else { isExpanded = getDependentView().getTranslationY() != 0; isScrolling = false; } } }; } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/widget/recyclerview/KLoadMoreView.kt ================================================ package gorden.krefreshlayout.demo.widget.recyclerview 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) } ================================================ FILE: app_kotlin/src/main/kotlin/gorden/krefreshlayout/demo/widget/recyclerview/KRecyclerView.kt ================================================ package gorden.krefreshlayout.demo.widget.recyclerview import android.content.Context import android.support.v4.util.SparseArrayCompat import android.support.v7.widget.GridLayoutManager import android.support.v7.widget.RecyclerView import android.support.v7.widget.StaggeredGridLayoutManager import android.util.AttributeSet import android.view.View import android.view.ViewGroup @Suppress("unused") /** * 支持添加Header Footer * 支持加载更多 */ class KRecyclerView(context: Context, attrs: AttributeSet?, defStyle: Int) : RecyclerView(context, attrs, defStyle) { private val ITEM_TYPE_HEADER_INIT = 100000 private val ITEM_TYPE_FOOTER_INIT = 200000 private val ITEM_TYPE_LOADMORE = 300000 var hasMore: Boolean = false set(value) { field = value if (value) showMore = true } var showMore:Boolean = false private var mLoading: Boolean = false var loadMoreEnable: Boolean = true private var mWrapAdapter: WrapAdapter = WrapAdapter() private var mInnerAdapter: Adapter? = null private var mLoadMoreView: KLoadMoreView? = null private val mHeaderViews = SparseArrayCompat() private val mFooterViews = SparseArrayCompat() private var mLoadMoreListener: ((RecyclerView) -> Unit)? = null constructor(context: Context) : this(context, null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) override fun setAdapter(adapter: Adapter?) { if (mInnerAdapter!=null){ mInnerAdapter?.unregisterAdapterDataObserver(mDataObserver) } mInnerAdapter = adapter mInnerAdapter?.registerAdapterDataObserver(mDataObserver) super.setAdapter(mWrapAdapter) } /** * 添加HeaderView */ fun addHeaderView(view: View) { mHeaderViews.put(mHeaderViews.size() + ITEM_TYPE_HEADER_INIT, view) mWrapAdapter.notifyDataSetChanged() } /** * 添加FooterView */ fun addFooterView(view: View) { mFooterViews.put(mFooterViews.size() + ITEM_TYPE_FOOTER_INIT, view) mWrapAdapter.notifyDataSetChanged() } /** * 设置LoadMoreView * 必须是一个视图View */ fun setLoadMoreView(loadMoreView: KLoadMoreView) { if (loadMoreView !is View) { throw IllegalStateException("KLoadMoreView must is a View?") } this.mLoadMoreView = loadMoreView mWrapAdapter.notifyDataSetChanged() removeOnScrollListener(defaultScrollListener) if (!(mLoadMoreView?.shouldLoadMore(this) ?: true)) { addOnScrollListener(defaultScrollListener) } } /** * 开始加载更多 */ fun startLoadMore() { if (!mLoading && loadMoreEnable && hasMore) { mLoading = true mLoadMoreView?.onLoadMore(this) mLoadMoreListener?.invoke(this) } } /** * 加载完成 */ fun loadMoreComplete(hasMore: Boolean) { mLoading = false mLoadMoreView?.onComplete(this, hasMore) this.hasMore = hasMore } fun loadMoreError(errorCode: Int) { mLoading = false mLoadMoreView?.onError(this, errorCode) } /** * 设置加载更多监听 */ fun setLoadMoreListener(loadMoreListener: (recyclerView: RecyclerView) -> Unit): Unit { mLoadMoreListener = loadMoreListener } /** * 默认的加载触发时机 */ private val defaultScrollListener = object : OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (!recyclerView.canScrollVertically(1)) { startLoadMore() } } } private inner class WrapAdapter : Adapter() { override fun onBindViewHolder(holder: ViewHolder, position: Int) { if (!isContent(position)) { return } mInnerAdapter?.onBindViewHolder(holder, position - mHeaderViews.size()) } override fun getItemCount(): Int { val adapterCount = mInnerAdapter?.itemCount ?: 0 if (adapterCount > 0) { return adapterCount + mHeaderViews.size() + mFooterViews.size() + if (mLoadMoreView == null||!showMore) 0 else 1 } else {//防止没有内容的时候加载更多显示出来 return mHeaderViews.size() + mFooterViews.size() } } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder? { if (mHeaderViews[viewType] != null) { return object : ViewHolder(mHeaderViews[viewType]) { init { itemView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) } } } if (viewType == ITEM_TYPE_LOADMORE) return object : ViewHolder(mLoadMoreView as View) { init { itemView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) } } if (mFooterViews[viewType] != null) { return object : ViewHolder(mFooterViews[viewType]) { init { itemView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) } } } return mInnerAdapter?.onCreateViewHolder(parent, viewType) } override fun getItemViewType(position: Int): Int { if (position < mHeaderViews.size()) { return mHeaderViews.keyAt(position) } if (mLoadMoreView != null && position == itemCount - 1&&showMore) { return ITEM_TYPE_LOADMORE } if (position >= mHeaderViews.size() + (mInnerAdapter?.itemCount ?: 0)) { return mFooterViews.keyAt(position - mHeaderViews.size() - (mInnerAdapter?.itemCount ?: 0)) } return mInnerAdapter?.getItemViewType(position - mHeaderViews.size()) ?: -1 } override fun onAttachedToRecyclerView(recyclerView: RecyclerView?) { mInnerAdapter?.onAttachedToRecyclerView(recyclerView) val layoutManager = layoutManager if (layoutManager is GridLayoutManager) { layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { if (!isContent(position)) { return layoutManager.spanCount } return 1 } } } } override fun onViewAttachedToWindow(holder: ViewHolder) { mInnerAdapter?.onViewAttachedToWindow(holder) val position = holder.layoutPosition val layoutParams = holder.itemView.layoutParams if (!isContent(position) && layoutParams != null && layoutParams is StaggeredGridLayoutManager.LayoutParams) { layoutParams.isFullSpan = true } } fun isContent(position: Int): Boolean { if (position < mHeaderViews.size()) return false if (mLoadMoreView != null && position == itemCount - 1) return false if (position >= mHeaderViews.size() + (mInnerAdapter?.itemCount ?: 0)) return false return true } } private val mDataObserver = object : AdapterDataObserver() { override fun onChanged() { mWrapAdapter.notifyDataSetChanged() } override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount) } override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload) } override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount) } override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { mWrapAdapter.notifyItemMoved(fromPosition, toPosition) } override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount) } } } ================================================ FILE: app_kotlin/src/main/res/drawable/bg_main.xml ================================================ ================================================ FILE: app_kotlin/src/main/res/drawable/selector_adpager_point.xml ================================================ ================================================ FILE: app_kotlin/src/main/res/layout/activity_header_list.xml ================================================