Repository: hougr/SmartisanPull Branch: master Commit: 99ef6cd89c31 Files: 27 Total size: 57.1 KB Directory structure: gitextract_9p1t0505/ ├── .gitignore ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── hougr/ │ │ └── smartisanpull/ │ │ └── ApplicationTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── hougr/ │ │ │ └── smartisanpull/ │ │ │ ├── MainActivity.java │ │ │ ├── RefreshStatus.java │ │ │ ├── SmartisanCircleView.java │ │ │ └── SmartisanRefreshableLayout.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── listview_item.xml │ │ │ └── refreshablelayout_header.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── com/ │ └── hougr/ │ └── smartisanpull/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /.idea *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures ================================================ FILE: README.md ================================================ # 锤子下拉 [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-SmartisanPull-green.svg?style=true)](https://android-arsenal.com/details/3/4257) “锤子下拉”,东半球最优雅的下拉控件。(也叫SmartisanRefreshableLayout。) 录屏gif 涉及到的知识点包括:自定义View、自定义ViewGroup、事件分发、属性动画等。 # 说明 本项目模仿“锤子阅读”的下拉效果。仅供学习交流,请勿用于商业用途。 Because the animation in my project imitates the one in Smartisan OS. Only communicate using, please do not used for commercial purposes or illegal purposes, thank you! # 介绍 下面是我做的过程中,每个阶段有意思的地方: (下面把“可刷新”的最小距离叫刷新距离) | 阶段 | 该阶段截图 | 该阶段说明 | | --------------- | ------------------ | --------------- | | 刚开始 | ![sea_invert](./screenshot/smartisan_pull0.png) | 下拉时先把item0上面的分隔线滚动出来,该分隔线在下拉过程中一直显示,直到header完全消失,它才重新藏起来。还有,到达刷新距离前,提示语逐渐清晰。另外,在任何阶段,如果手指向上返回,动画逐渐回到原始状态。 | | 到达刷新距离前 | ![sea_invert](./screenshot/smartisan_pull1.png) | 两线段逐渐过渡为圆弧。 | | 到达刷新距离时 | ![sea_invert](./screenshot/smartisan_pull2.png) | 两圆弧刚好各转半圈,两圆弧间的两个缺口处于同一水平线。 | | 到达刷新距离以下 | ![sea_invert](./screenshot/smartisan_pull3.png) | 刷新距离以下,摩擦系数越来越大。但是,两圆弧的旋转始终是平滑的,只有速度变化。 | | 刷新完成后 | ![sea_invert](./screenshot/smartisan_pull4.png) | 最后,两圆弧逐渐过渡成线段,消失在两端。 | # 使用 ### 首先,在布局文件中使用SmartisanRefreshableLayout 只需在里面加入ListView,仍然保持了ListView的正常使用,不会对它造成什么影响。 ``` ``` ### 然后,实现下拉事件的监听 ``` mSmartisanRefreshableLayout = (SmartisanRefreshableLayout) findViewById(R.id.refreshable_view); mSmartisanRefreshableLayout.setOnRefreshListener(new SmartisanRefreshableLayout.PullToRefreshListener() { @Override public void onRefresh() { try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void onRefreshFinished() { mSmartisanRefreshableLayout.finishRefreshing(); mViewHolderAdapter.addToListHead((mRefreshCount++)+" 喜欢的话,可以在github上赏我一颗star"); } }); ``` # 接下来 接下来,会增加对RecyclerView的支持。 # 拜托 喜欢的话,可以点击右上角的star。感谢。 # License 代码版权 The MIT License (MIT) Copyright (c) 2016 hougr Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 24 buildToolsVersion "24.0.1" defaultConfig { applicationId "com.hougr.smartisanpull" minSdkVersion 15 targetSdkVersion 24 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.1.1' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/hougr/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: app/src/androidTest/java/com/hougr/smartisanpull/ApplicationTest.java ================================================ package com.hougr.smartisanpull; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/hougr/smartisanpull/MainActivity.java ================================================ package com.hougr.smartisanpull; import android.content.Context; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import java.util.LinkedList; import java.util.List; public class MainActivity extends AppCompatActivity { private Toolbar mToolbar; private SmartisanRefreshableLayout mSmartisanRefreshableLayout; private ListView mListView; private int mRefreshCount =0; private LinkedList mStringList; private ViewHolderAdapter mViewHolderAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mToolbar = (Toolbar) findViewById(R.id.toolBar); mToolbar.setTitle(""); setSupportActionBar(mToolbar); mSmartisanRefreshableLayout = (SmartisanRefreshableLayout) findViewById(R.id.refreshable_view); mSmartisanRefreshableLayout.setOnRefreshListener(new SmartisanRefreshableLayout.PullToRefreshListener() { @Override public void onRefresh() { try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void onRefreshFinished() { mSmartisanRefreshableLayout.finishRefreshing(); mViewHolderAdapter.addToListHead((mRefreshCount++)+" 喜欢的话,可以在github上赏我一颗star"); } }); mStringList = new LinkedList<>(); mStringList.add("“锤子下拉”,东半球最优雅的下拉控件"); mStringList.add("https://github.com/hougr/SmartisanPull"); mStringList.add("模仿“锤子阅读”的下拉效果"); mStringList.add("仅供学习交流,请勿用于商业用途。"); mStringList.add("----------分隔线----------"); mStringList.add("下面是我做的过程中觉得有意思的地方:"); mStringList.add("(下面把“可刷新”的最小距离叫刷新距离)"); mStringList.add("1、下拉时先把item0上面的分隔线滚动出来,"); mStringList.add(" 该分隔线在下拉过程中一直显示,"); mStringList.add(" 直到header完全消失,它才重新藏起来"); mStringList.add("2、到达刷新距离前,提示语逐渐清晰"); mStringList.add("3、到达刷新距离时,两圆弧刚好各转半圈,"); mStringList.add(" 两圆弧间的两个缺口处于同一水平线"); mStringList.add("4、刷新距离以下,摩擦系数越来越大"); mStringList.add("5、如果手指向上返回,动画逐渐回到原始状态"); mStringList.add("6、两圆弧的旋转始终是平滑的,只有速度变化"); mStringList.add("7、最后,两圆弧逐渐过渡成线段,消失在两端"); mStringList.add("喜欢的话,可以在github上赏我一颗star"); mListView = (ListView) findViewById(R.id.list_view); mViewHolderAdapter = new ViewHolderAdapter(getApplicationContext(),mStringList); mListView.setAdapter(mViewHolderAdapter); } private class ViewHolderAdapter extends BaseAdapter { public List mItemStringList; public LayoutInflater mLayoutInflater; ViewHolderAdapter(Context context, List stringList){ mItemStringList =stringList; mLayoutInflater = LayoutInflater.from(context); } public void addToListHead(String newItemString){ mItemStringList.add(0,newItemString); notifyDataSetChanged(); } @Override public int getCount() { if(mItemStringList ==null){ return 0; } return mItemStringList.size(); } @Override public Object getItem(int i) { return mItemStringList.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View view, ViewGroup viewGroup) { final String itemString= mItemStringList.get(i); ViewHolder viewHolder; if(view == null){ viewHolder=new ViewHolder(); view = mLayoutInflater.inflate(R.layout.listview_item,null); viewHolder.mItemView = view; viewHolder.mTextView =(TextView)view.findViewById(R.id.textView); view.setTag(viewHolder); }else { viewHolder=(ViewHolder) view.getTag(); } viewHolder.mTextView.setText(itemString); viewHolder.mItemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getApplicationContext(),"点击了:"+itemString,Toast.LENGTH_SHORT).show(); } }); return view; } public final class ViewHolder{ View mItemView; TextView mTextView; } } } ================================================ FILE: app/src/main/java/com/hougr/smartisanpull/RefreshStatus.java ================================================ package com.hougr.smartisanpull; /** * Created by hougr on 16/8/29. */ public class RefreshStatus{ //四种状态。 public static final int STATUS_ORIGIN = 0; public static final int STATUS_DISTANCE_UNFINISHED = 1; public static final int STATUS_DISTANCE_UNFINISHED_BACK = 2; public static final int STATUS_DISTANCE_FINISHED = 3; public static final int STATUS_REFRESH_PREPARE = 4; public static final int STATUS_REFRESHING =5; public static final int STATUS_REFRESH_FINISHED_CIRCLE = 6; // public static final int STATUS_REFRESH_FINISHED_HIDE = 7; } ================================================ FILE: app/src/main/java/com/hougr/smartisanpull/SmartisanCircleView.java ================================================ package com.hougr.smartisanpull; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; /** * Created by hougr on 16/8/25. */ public class SmartisanCircleView extends View { // public static final float LINE_LENGTH = 45; // private static final float ARROW_LENGTH = 8; // public static final float CIRCLE_RADIUS = 20; float Width; float Height; public float mLineLength; public float mArrowLength; public float mCircleRadius; public float mStrokeWidth; public float mLineSpaceHalf; Paint mPaint; private int mRefreshStatus; private float mAnimatorDistance; // private long animatorDuration = 5000; // private TimeInterpolator timeInterpolator = new DecelerateInterpolator(); // private TimeInterpolator timeInterpolator = new LinearInterpolator(); public SmartisanCircleView(Context context, AttributeSet attrs) { super(context, attrs); mRefreshStatus = RefreshStatus.STATUS_ORIGIN; mAnimatorDistance =0; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); Width = getWidth(); Height = getHeight(); mCircleRadius = Height/2f/(float) (2* Math.PI); mStrokeWidth = mCircleRadius/3f; mArrowLength = mStrokeWidth * 1.2f; mLineLength = mCircleRadius * (float) Math.PI * 6.2f/8f; mLineSpaceHalf = (mCircleRadius * (float) Math.PI - mLineLength)/2; mPaint=new Paint(); mPaint.setStyle(Paint.Style.STROKE);//设置画笔样式为描边,如果已经设置,可以忽略 // mPaint.setColor(Color.GREEN); mPaint.setColor(Color.argb(255,200, 200, 200)); mPaint.setStrokeCap(Paint.Cap.ROUND);//圆角笔触 // mPaint.setStrokeWidth(10); mPaint.setStrokeWidth(mStrokeWidth); // updateCircleView(animatorDuration); } // public float getViewHeight(){ // return Height; // } public void setStatusAndAnimatorDistance(int status, float animatorDistance){ mRefreshStatus = status; mAnimatorDistance = animatorDistance; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.translate(Width/2,Height/2); switch (mRefreshStatus){ case RefreshStatus.STATUS_ORIGIN: break; case RefreshStatus.STATUS_DISTANCE_UNFINISHED: drawDistanceFinished(canvas); break; case RefreshStatus.STATUS_DISTANCE_UNFINISHED_BACK: drawDistanceFinished(canvas); break; case RefreshStatus.STATUS_DISTANCE_FINISHED: drawDistanceFinished(canvas); break; case RefreshStatus.STATUS_REFRESH_PREPARE: drawRefreshing(canvas); break; case RefreshStatus.STATUS_REFRESHING: drawRefreshing(canvas); break; case RefreshStatus.STATUS_REFRESH_FINISHED_CIRCLE: drawRefreshFinished(canvas); break; // case RefreshStatus.STATUS_REFRESH_FINISHED_HIDE: // drawRefreshFinished(canvas); // break; default: break; } } private void drawDistanceFinished(Canvas canvas){ mAnimatorDistance = mAnimatorDistance - mLineSpaceHalf*2; if(mAnimatorDistance < Height/2 ){ float leftStartX = -mCircleRadius; float leftStartY = Height/4 - mAnimatorDistance/2; float leftStopX = leftStartX; float leftStopY = leftStartY + mLineLength; canvas.drawLine(leftStartX, leftStartY, leftStopX, leftStopY, mPaint); float leftArrowX = leftStartX - mArrowLength * (float) Math.sin(Math.toRadians(30)); float leftArrowY = leftStartY + mArrowLength * (float) Math.cos(Math.toRadians(30)); canvas.drawLine(leftStartX, leftStartY, leftArrowX, leftArrowY, mPaint); float rightStartX = -leftStartX; float rightStartY = -leftStartY; float rightStopX = -leftStopX; float rightStopY = -leftStopY; canvas.drawLine(rightStartX,rightStartY,rightStopX,rightStopY,mPaint); float rightArrowX = -leftArrowX; float rightArrowY = -leftArrowY; canvas.drawLine(rightStartX, rightStartY, rightArrowX, rightArrowY, mPaint); }else{ float circleValue = (mAnimatorDistance - Height/2)/2; float radius = mCircleRadius; RectF rectF = new RectF(-radius,-radius,radius,radius); float swipeAngle = (float) Math.toDegrees(circleValue/radius); float totalAngle = (float) Math.toDegrees(mLineLength/radius); float leftArcStartX = mCircleRadius * (float) Math.cos(Math.toRadians(180+swipeAngle)); float leftArcStartY = mCircleRadius * (float) Math.sin(Math.toRadians(180+swipeAngle)); float leftArcArrowX = leftArcStartX - mArrowLength * (float) Math.sin(Math.toRadians(swipeAngle+30)); float leftArcArrowY = leftArcStartY + mArrowLength * (float) Math.cos(Math.toRadians(swipeAngle+30)); canvas.drawLine(leftArcStartX, leftArcStartY, leftArcArrowX, leftArcArrowY, mPaint); float rightArcStartX = mCircleRadius * (float) Math.cos(Math.toRadians(swipeAngle)); float rightArcStartY = mCircleRadius * (float) Math.sin(Math.toRadians(swipeAngle)); float rightArcArrowX = rightArcStartX + mArrowLength * (float) Math.sin(Math.toRadians(swipeAngle+30)); float rightArcArrowY = rightArcStartY - mArrowLength * (float) Math.cos(Math.toRadians(swipeAngle+30)); canvas.drawLine(rightArcStartX, rightArcStartY, rightArcArrowX, rightArcArrowY, mPaint); if(circleValue < totalAngle/360 *(float) Math.PI * mCircleRadius*2 ){ canvas.drawArc(rectF,180,swipeAngle,false,mPaint); canvas.drawArc(rectF,0,swipeAngle,false,mPaint); float leftStartX = -mCircleRadius; float leftStartY = 0; float leftStopX = leftStartX; float leftStopY = mLineLength - circleValue; canvas.drawLine(leftStartX, leftStartY, leftStopX, leftStopY, mPaint); float rightStartX = -leftStartX; float rightStartY = -leftStartY; float rightStopX = -leftStopX; float rightStopY = -leftStopY; canvas.drawLine(rightStartX, rightStartY, rightStopX, rightStopY, mPaint); }else { canvas.drawArc(rectF,swipeAngle-totalAngle,totalAngle,false,mPaint); canvas.drawArc(rectF,swipeAngle+180-totalAngle,totalAngle,false,mPaint); } } } private void drawRefreshing(Canvas canvas){ mAnimatorDistance = mAnimatorDistance - mLineSpaceHalf*2; float circleValue = (mAnimatorDistance - Height/2)/2; float radius = mCircleRadius; RectF rectF = new RectF(-radius,-radius,radius,radius); float swipeAngle = (float) Math.toDegrees(circleValue/radius); float totalAngle = (float) Math.toDegrees(mLineLength/radius); float leftArcStartX = mCircleRadius * (float) Math.cos(Math.toRadians(180+swipeAngle)); float leftArcStartY = mCircleRadius * (float) Math.sin(Math.toRadians(180+swipeAngle)); float leftArcArrowX = leftArcStartX - mArrowLength * (float) Math.sin(Math.toRadians(swipeAngle+30)); float leftArcArrowY = leftArcStartY + mArrowLength * (float) Math.cos(Math.toRadians(swipeAngle+30)); canvas.drawLine(leftArcStartX, leftArcStartY, leftArcArrowX, leftArcArrowY, mPaint); float rightArcStartX = mCircleRadius * (float) Math.cos(Math.toRadians(swipeAngle)); float rightArcStartY = mCircleRadius * (float) Math.sin(Math.toRadians(swipeAngle)); float rightArcArrowX = rightArcStartX + mArrowLength * (float) Math.sin(Math.toRadians(swipeAngle+30)); float rightArcArrowY = rightArcStartY - mArrowLength * (float) Math.cos(Math.toRadians(swipeAngle+30)); canvas.drawLine(rightArcStartX, rightArcStartY, rightArcArrowX, rightArcArrowY, mPaint); canvas.drawArc(rectF,swipeAngle-totalAngle,totalAngle,false,mPaint); canvas.drawArc(rectF,swipeAngle+180-totalAngle,totalAngle,false,mPaint); } private void drawRefreshFinished(Canvas canvas){ mAnimatorDistance = mAnimatorDistance - mLineSpaceHalf; if(mAnimatorDistance < (float) Math.PI * mCircleRadius){ float radius = mCircleRadius; RectF rectF = new RectF(-radius,-radius,radius,radius); float swipeAngle = (float) Math.toDegrees(mAnimatorDistance/radius); float totalAngle = (float) Math.toDegrees(mLineLength/radius); float leftArcStartX = mCircleRadius * (float) Math.cos(Math.toRadians(180+swipeAngle)); float leftArcStartY = mCircleRadius * (float) Math.sin(Math.toRadians(180+swipeAngle)); float leftArcArrowX = leftArcStartX - mArrowLength * (float) Math.sin(Math.toRadians(swipeAngle+30)); float leftArcArrowY = leftArcStartY + mArrowLength * (float) Math.cos(Math.toRadians(swipeAngle+30)); canvas.drawLine(leftArcStartX, leftArcStartY, leftArcArrowX, leftArcArrowY, mPaint); float rightArcStartX = mCircleRadius * (float) Math.cos(Math.toRadians(swipeAngle)); float rightArcStartY = mCircleRadius * (float) Math.sin(Math.toRadians(swipeAngle)); float rightArcArrowX = rightArcStartX + mArrowLength * (float) Math.sin(Math.toRadians(swipeAngle+30)); float rightArcArrowY = rightArcStartY - mArrowLength * (float) Math.cos(Math.toRadians(swipeAngle+30)); canvas.drawLine(rightArcStartX, rightArcStartY, rightArcArrowX, rightArcArrowY, mPaint); canvas.drawArc(rectF,swipeAngle-totalAngle,totalAngle,false,mPaint); canvas.drawArc(rectF,swipeAngle+180-totalAngle,totalAngle,false,mPaint); }else if(mAnimatorDistance < (float) Math.PI * mCircleRadius + mLineLength){ float nonCircleValue = mAnimatorDistance - (float) Math.PI * mCircleRadius; float leftStartX = -mCircleRadius; float leftStartY = -nonCircleValue; float leftStopX = leftStartX; float leftStopY = 0; canvas.drawLine(leftStartX, leftStartY, leftStopX, leftStopY, mPaint); float leftArcArrowX = leftStartX - mArrowLength * (float) Math.sin(Math.toRadians(30)); float leftArcArrowY = leftStartY + mArrowLength * (float) Math.cos(Math.toRadians(30)); canvas.drawLine(leftStartX, leftStartY, leftArcArrowX, leftArcArrowY, mPaint); float rightStartX = -leftStartX; float rightStartY = -leftStartY; float rightStopX = -leftStopX; float rightStopY = -leftStopY; canvas.drawLine(rightStartX, rightStartY, rightStopX, rightStopY, mPaint); float rightArcArrowX = rightStartX + mArrowLength * (float) Math.sin(Math.toRadians(30)); float rightArcArrowY = rightStartY - mArrowLength * (float) Math.cos(Math.toRadians(30)); canvas.drawLine(rightStartX, rightStartY, rightArcArrowX, rightArcArrowY, mPaint); float radius = mCircleRadius; RectF rectF = new RectF(-radius,-radius,radius,radius); float swipeAngle = (float) Math.toDegrees((mLineLength-nonCircleValue)/radius); float smallAngle = 180 - swipeAngle; canvas.drawArc(rectF,smallAngle,swipeAngle,false,mPaint); canvas.drawArc(rectF,smallAngle+180,swipeAngle,false,mPaint); }else{ mAnimatorDistance = mAnimatorDistance - (float) Math.PI * mCircleRadius - mLineLength; float leftStopX = -mCircleRadius; float leftStopY = - mAnimatorDistance; float leftStartX = leftStopX; float leftStartY = leftStopY - mLineLength; canvas.drawLine(leftStartX, leftStartY, leftStopX, leftStopY, mPaint); float leftArrowX = leftStartX - mArrowLength * (float) Math.sin(Math.toRadians(30)); float leftArrowY = leftStartY + mArrowLength * (float) Math.cos(Math.toRadians(30)); canvas.drawLine(leftStartX, leftStartY, leftArrowX, leftArrowY, mPaint); float rightStartX = -leftStartX; float rightStartY = -leftStartY; float rightStopX = -leftStopX; float rightStopY = -leftStopY; canvas.drawLine(rightStartX,rightStartY,rightStopX,rightStopY,mPaint); float rightArrowX = -leftArrowX; float rightArrowY = -leftArrowY; canvas.drawLine(rightStartX, rightStartY, rightArrowX, rightArrowY, mPaint); } } // protected float dp2px(float dp){ // return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); // } } ================================================ FILE: app/src/main/java/com/hougr/smartisanpull/SmartisanRefreshableLayout.java ================================================ package com.hougr.smartisanpull; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Color; import android.os.AsyncTask; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; /** * Created by hougr on 16/8/25. */ public class SmartisanRefreshableLayout extends LinearLayout { private static final float ZERO_FOR_COMPARE=0.005f; //所有子View private RelativeLayout mHeaderLayout; private SmartisanCircleView mCircleView; private TextView mDescriptionTextView; private ListView mListView; //布局相关 private boolean mIsLayoutLoaded; //是否已加载过一次layout private int mHeaderHeight; private MarginLayoutParams mHeaderMargin; //下拉头的布局参数 private ValueAnimator mCircleAnimator; private ValueAnimator mPulledAnimator; //事件相关 private int mCurrentStatus; private boolean mInterceptBoolean = false; //当前事件是否拦截 private float mLastMoveY; private float mPulledDistance; //HeadView被下拉的距离,不是手指移动距离 private float mAnimatorDistance; private float mRubRatio = 1.0f; //摩擦系数 private float mListDividerHeight; private PullToRefreshListener mListener; //下拉刷新的回调接口 public SmartisanRefreshableLayout(Context context, AttributeSet attrs) { super(context, attrs); mHeaderLayout = (RelativeLayout) LayoutInflater.from(context).inflate(R.layout.refreshablelayout_header,null,true); mCircleView = (SmartisanCircleView) mHeaderLayout.findViewById(R.id.smartisanView); mDescriptionTextView = (TextView) mHeaderLayout.findViewById(R.id.descriptionTextView); setOrientation(VERTICAL); addView(mHeaderLayout, 0); mPulledDistance=0; mAnimatorDistance=0; } public void setOnRefreshListener(PullToRefreshListener listener) { mListener = listener; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed && !mIsLayoutLoaded) { mHeaderMargin = (MarginLayoutParams) mHeaderLayout.getLayoutParams(); mHeaderHeight = mHeaderLayout.getHeight(); mHeaderMargin.topMargin = -mHeaderHeight/2; mHeaderMargin.bottomMargin = mHeaderMargin.topMargin; mHeaderLayout.setLayoutParams(mHeaderMargin); mListView = (ListView) getChildAt(1); //这句需要改进 mListDividerHeight = mListView.getDividerHeight(); this.setBackgroundColor(Color.argb(255,255,255,255)); //把背景统一设置为白色 mCurrentStatus = RefreshStatus.STATUS_ORIGIN; updateLayoutAndText(); mIsLayoutLoaded = true; } } @Override public boolean onInterceptTouchEvent(MotionEvent event) { int x = (int) event.getRawX(); int y = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mInterceptBoolean = false; //避免不下拉,只是往下翻列表。因为此时不知道手势往上还是往下。 break; } case MotionEvent.ACTION_MOVE: { View firstChildView = mListView.getChildAt(0); //用于判断ListView是否没有元素 if(firstChildView == null || (firstChildView != null && mListView.getFirstVisiblePosition() == 0 && mListView.getChildAt(0).getTop() == 0)){ //万事俱备,只欠下拉。 float currentRawY = event.getRawY(); float tmpMoveY = currentRawY - mLastMoveY; if((isEqualZero(mPulledDistance) && tmpMoveY >0) || (mPulledDistance > 0 && mCurrentStatus != RefreshStatus.STATUS_REFRESHING)) { //两种情况:第一次下拉、已经下拉过了。 // 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新 Log.d("是否拦截",":可下拉"); if(!mInterceptBoolean){ mLastMoveY = event.getRawY(); } mInterceptBoolean = true; // // 当前正处于下拉或释放状态,要让ListView失去焦点,否则被点击的那一项会一直处于选中状态 // mListView.setPressed(false); // mListView.setFocusable(false); // mListView.setFocusableInTouchMode(false); // // 当前正处于下拉或释放状态,通过返回true屏蔽掉ListView的滚动事件 }else if(mCurrentStatus == RefreshStatus.STATUS_REFRESHING && tmpMoveY > 0){ mInterceptBoolean =false; }else if(isEqualZero(mPulledDistance) && tmpMoveY < 0){ } else{ mInterceptBoolean = false; } } else { mInterceptBoolean = false; } break; } case MotionEvent.ACTION_UP: { mInterceptBoolean = false; break; } default: break; } mLastMoveY = y; Log.d("是否拦截",":"+mInterceptBoolean); return mInterceptBoolean; } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: float currentRawY = event.getRawY(); float tmpMoveY = currentRawY - mLastMoveY; if(mPulledDistance < 0 || isEqualZero(mPulledDistance)){ mPulledDistance += tmpMoveY; }else { if(mPulledDistance <= mHeaderHeight){ mCurrentStatus = RefreshStatus.STATUS_DISTANCE_UNFINISHED; mRubRatio = 1.0f; }else { mCurrentStatus = RefreshStatus.STATUS_DISTANCE_FINISHED; mRubRatio = (2-mPulledDistance/(float) mHeaderHeight)/4; } mPulledDistance += mRubRatio * tmpMoveY; mAnimatorDistance = mPulledDistance; updateAllView(mCurrentStatus,mPulledDistance,mAnimatorDistance); mLastMoveY = currentRawY; } break; case MotionEvent.ACTION_UP: default: Log.d("事件","Action_up"); if (mCurrentStatus == RefreshStatus.STATUS_DISTANCE_FINISHED) { mCurrentStatus = RefreshStatus.STATUS_REFRESH_PREPARE; updateAllView(mCurrentStatus, mPulledDistance,mAnimatorDistance); new RefreshingTask().execute(); } else if (mCurrentStatus == RefreshStatus.STATUS_DISTANCE_UNFINISHED) { mCurrentStatus = RefreshStatus.STATUS_DISTANCE_UNFINISHED_BACK; updateAllView(mCurrentStatus, mPulledDistance,mAnimatorDistance); // new HideHeaderTask().execute(); } break; } return true; } private void updateLayoutAndText(){ float mockPulledDistance; if(mPulledDistance <= mListDividerHeight){ mListView.scrollTo(0,(int)mPulledDistance); mockPulledDistance =0; }else{ mListView.scrollTo(0,-mListView.getDividerHeight()); mockPulledDistance = mPulledDistance-mListDividerHeight; } mHeaderMargin.topMargin = (int)(-mHeaderHeight/2 + mockPulledDistance/2); mHeaderMargin.bottomMargin = mHeaderMargin.topMargin; mHeaderLayout.setLayoutParams(mHeaderMargin); switch (mCurrentStatus){ case RefreshStatus.STATUS_ORIGIN: mDescriptionTextView.setTextColor(Color.argb(0,120,120,120)); break; case RefreshStatus.STATUS_DISTANCE_UNFINISHED: case RefreshStatus.STATUS_DISTANCE_UNFINISHED_BACK: mDescriptionTextView.setText("下拉即可刷新..."); if(mockPulledDistance < mHeaderHeight/4){ mDescriptionTextView.setTextColor(Color.argb(0,120,120,120)); }else { mDescriptionTextView.setTextColor(Color.argb((int)((mockPulledDistance- (float) mHeaderHeight/4)/(mHeaderHeight*3/4)*255),120,120,120)); } break; case RefreshStatus.STATUS_DISTANCE_FINISHED: mDescriptionTextView.setText("松开即可刷新..."); mDescriptionTextView.setTextColor(Color.argb(255,120,120,120)); break; case RefreshStatus.STATUS_REFRESH_PREPARE: case RefreshStatus.STATUS_REFRESHING: mDescriptionTextView.setText("正在刷新列表..."); mDescriptionTextView.setTextColor(Color.argb(255,120,120,120)); break; case RefreshStatus.STATUS_REFRESH_FINISHED_CIRCLE: // case RefreshStatus.STATUS_REFRESH_FINISHED_HIDE: mDescriptionTextView.setText("正在刷新列表..."); if(mockPulledDistance < mHeaderHeight/4){ mDescriptionTextView.setTextColor(Color.argb(0,120,120,120)); }else { mDescriptionTextView.setTextColor(Color.argb((int)((mockPulledDistance- (float) mHeaderHeight/4)/(mHeaderHeight*3/4)*255),120,120,120)); } break; default: break; } } public void updateAllView(int refreshStatus, float pulledDistance , final float animatorDistance){ Log.d("更新视图",refreshStatus+","+pulledDistance); mCurrentStatus = refreshStatus; mPulledDistance = pulledDistance; mAnimatorDistance = animatorDistance; mCircleView.setStatusAndAnimatorDistance(refreshStatus, animatorDistance); updateLayoutAndText(); switch (mCurrentStatus){ case RefreshStatus.STATUS_ORIGIN: break; case RefreshStatus.STATUS_DISTANCE_UNFINISHED: mCircleView.setStatusAndAnimatorDistance(RefreshStatus.STATUS_DISTANCE_UNFINISHED, mAnimatorDistance); mCircleView.invalidate(); break; case RefreshStatus.STATUS_DISTANCE_UNFINISHED_BACK: resetCircleAnimator(mAnimatorDistance, 0, 300, 0, new UpdateHeaderViewCallback() { @Override public void onAnimationUpdate(float animatorValue) { Log.d("属性动画过程值:","是"+ animatorValue); mPulledDistance = animatorValue; mAnimatorDistance = animatorValue; mCircleView.setStatusAndAnimatorDistance(RefreshStatus.STATUS_DISTANCE_UNFINISHED_BACK, mAnimatorDistance); mCircleView.invalidate(); updateLayoutAndText(); } @Override public void onAnimationEnd() { mCurrentStatus = RefreshStatus.STATUS_ORIGIN; mPulledDistance =0; mAnimatorDistance =mPulledDistance; mCircleView.setStatusAndAnimatorDistance(RefreshStatus.STATUS_ORIGIN, 0); } }); break; case RefreshStatus.STATUS_DISTANCE_FINISHED: mCircleView.invalidate(); break; case RefreshStatus.STATUS_REFRESH_PREPARE: resetPullAnimator(mPulledDistance, mHeaderHeight, 200, 0, new UpdateHeaderViewCallback() { @Override public void onAnimationUpdate(float animatorValue) { mPulledDistance =animatorValue; updateLayoutAndText(); } @Override public void onAnimationEnd() { mCurrentStatus = RefreshStatus.STATUS_REFRESHING; updateAllView(RefreshStatus.STATUS_REFRESHING, mCircleView.getHeight(),mAnimatorDistance); } }); resetCircleAnimator(mPulledDistance, mPulledDistance+ (float) (Math.PI * mCircleView.mCircleRadius*2), 350, -1, new UpdateHeaderViewCallback() { @Override public void onAnimationUpdate(float animatorValue) { mAnimatorDistance =animatorValue; mCircleView.setStatusAndAnimatorDistance(RefreshStatus.STATUS_REFRESHING, mAnimatorDistance); mCircleView.invalidate(); } @Override public void onAnimationEnd() { } }); break; case RefreshStatus.STATUS_REFRESHING: break; case RefreshStatus.STATUS_REFRESH_FINISHED_CIRCLE: float doubleCircleCount = (mAnimatorDistance - mHeaderHeight/2)/((float) Math.PI * mCircleView.mCircleRadius * 2); float circleCount = doubleCircleCount/2; float smallCircle = circleCount % 1; Log.d("刷新后","smallCircle:"+smallCircle); if(smallCircle >0.5){ smallCircle -=0.5; } float nonCircleCount = 0.5f - smallCircle; float toDoCircleDistance = nonCircleCount * ((float) Math.PI * mCircleView.mCircleRadius * 2); final float finalTotalDistance = (float) Math.PI * mCircleView.mCircleRadius + mCircleView.mLineLength+mHeaderHeight/4; final float toDoTotalDistance = toDoCircleDistance + mCircleView.mLineLength+mHeaderHeight/4; final float bili = mHeaderHeight / toDoTotalDistance; final float doneDistance = finalTotalDistance - toDoTotalDistance; mAnimatorDistance = doneDistance; resetCircleAnimator(mAnimatorDistance, finalTotalDistance, 200, 0, new UpdateHeaderViewCallback() { @Override public void onAnimationUpdate(float animatorValue) { mAnimatorDistance=animatorValue; mPulledDistance = (finalTotalDistance - animatorValue) * bili; mCircleView.setStatusAndAnimatorDistance(RefreshStatus.STATUS_REFRESH_FINISHED_CIRCLE, mAnimatorDistance); mCircleView.invalidate(); updateLayoutAndText(); } @Override public void onAnimationEnd() { mCurrentStatus = RefreshStatus.STATUS_ORIGIN; mPulledDistance =0; mAnimatorDistance =mPulledDistance; mCircleView.setStatusAndAnimatorDistance(RefreshStatus.STATUS_ORIGIN, 0); } }); break; default: break; } } private void resetCircleAnimator(float startValue, float endValue, int duration , int repeatCount, final UpdateHeaderViewCallback updateHeaderViewCallback){ if (mCircleAnimator !=null && mCircleAnimator.isRunning()){ mCircleAnimator.cancel(); } mCircleAnimator = ValueAnimator.ofFloat(startValue, endValue).setDuration(duration); mCircleAnimator.setInterpolator(new LinearInterpolator()); mCircleAnimator.setRepeatCount(repeatCount); mCircleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float animatedValue = (float) animation.getAnimatedValue(); updateHeaderViewCallback.onAnimationUpdate(animatedValue); } }); mCircleAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { updateHeaderViewCallback.onAnimationEnd(); animation.cancel(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mCircleAnimator.start(); } private void resetPullAnimator(float startValue, float endValue, int duration , int repeatCount, final UpdateHeaderViewCallback updateHeaderViewCallback){ if (mPulledAnimator !=null && mPulledAnimator.isRunning()){ mPulledAnimator.cancel(); } mPulledAnimator = ValueAnimator.ofFloat(startValue, endValue).setDuration(duration); mPulledAnimator.setInterpolator(new LinearInterpolator()); mPulledAnimator.setRepeatCount(repeatCount); mPulledAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float animatedValue = (float) animation.getAnimatedValue(); updateHeaderViewCallback.onAnimationUpdate(animatedValue); } }); mPulledAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { updateHeaderViewCallback.onAnimationEnd(); animation.cancel(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mPulledAnimator.start(); } public void finishRefreshing(){ mCurrentStatus = RefreshStatus.STATUS_REFRESH_FINISHED_CIRCLE; updateAllView(mCurrentStatus, mHeaderHeight, mAnimatorDistance); } private static interface UpdateHeaderViewCallback{ public void onAnimationUpdate(float animatorValue); public void onAnimationEnd(); } public interface PullToRefreshListener { public void onRefresh(); public void onRefreshFinished(); } class RefreshingTask extends AsyncTask { @Override protected Void doInBackground(Void... params) { if (mListener != null) { mListener.onRefresh(); } return null; } @Override protected void onProgressUpdate(Float... topMargin) { } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); if (mListener != null) { mListener.onRefreshFinished(); } } } private boolean isEqualZero(float floatValue){ if(floatValue < ZERO_FOR_COMPARE && floatValue > -ZERO_FOR_COMPARE){ return true; }else { return false; } } } ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: app/src/main/res/layout/listview_item.xml ================================================ ================================================ FILE: app/src/main/res/layout/refreshablelayout_header.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ #ffffff #000000 #FF4081 #333333 ================================================ FILE: app/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: app/src/main/res/values/strings.xml ================================================ 锤子下拉 ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: app/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: app/src/test/java/com/hougr/smartisanpull/ExampleUnitTest.java ================================================ package com.hougr.smartisanpull; import org.junit.Test; import static org.junit.Assert.*; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Mon Dec 28 10:00:20 PST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: settings.gradle ================================================ include ':app'