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