├── .DS_Store ├── .gitignore ├── README.md ├── YanXuanRefresh ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── sheng │ │ │ └── yanxuanrefresh │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── sheng │ │ │ │ └── yanxuanrefresh │ │ │ │ ├── MainActivity.java │ │ │ │ ├── YXRefreshHeader.java │ │ │ │ └── refresh │ │ │ │ ├── Point.java │ │ │ │ ├── ViewStatus.java │ │ │ │ └── YanXuanRefreshView.java │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ └── yx_refresh_header.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── all_refresh_de_ic.png │ │ │ ├── all_refresh_gui_ic.png │ │ │ ├── all_refresh_hao_ic.png │ │ │ ├── all_refresh_huo_ic.png │ │ │ ├── all_refresh_me_ic.png │ │ │ ├── all_refresh_mei_ic.png │ │ │ ├── all_refresh_na_ic.png │ │ │ ├── all_refresh_sheng_ic.png │ │ │ ├── all_refresh_yanxuan_ic.png │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── sheng │ │ └── yanxuanrefresh │ │ └── ExampleUnitTest.java └── build.gradle └── refresh.gif /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | YanXuanRefresh/.idea/compiler.xml 3 | 4 | .DS_Store 5 | 6 | .DS_Store 7 | 8 | YanXuanRefresh/.idea/copyright/profiles_settings.xml 9 | 10 | YanXuanRefresh/.idea/gradle.xml 11 | 12 | .DS_Store 13 | 14 | YanXuanRefresh/.idea/misc.xml 15 | 16 | YanXuanRefresh/.idea/modules.xml 17 | 18 | YanXuanRefresh/.idea/runConfigurations.xml 19 | 20 | YanXuanRefresh/gradle.properties 21 | 22 | YanXuanRefresh/gradle/wrapper/gradle-wrapper.jar 23 | 24 | YanXuanRefresh/gradle/wrapper/gradle-wrapper.properties 25 | 26 | YanXuanRefresh/gradlew 27 | 28 | YanXuanRefresh/gradlew.bat 29 | 30 | YanXuanRefresh/settings.gradle 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YanXuanRefresh 2 | 仿网易严选下拉刷新动画效果 3 | 4 | [下拉刷新框架的地址](https://github.com/scwang90/SmartRefreshLayout) 5 | 6 | 代码中有注释,实现起来还是比较简单的,还有一小点问题待完善,后续会写一篇文章简单讲解一下我的思路。喜欢的话给个star吧,谢谢😊 7 | 8 | # 效果展示 9 | 10 | ![image](https://github.com/ChaserSheng/YanXuanRefresh/blob/master/refresh.gif) 11 | -------------------------------------------------------------------------------- /YanXuanRefresh/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | defaultConfig { 7 | applicationId "com.sheng.yanxuanrefresh" 8 | minSdkVersion 12 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.3.1' 28 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 29 | testCompile 'junit:junit:4.12' 30 | compile 'com.scwang.smartrefresh:SmartRefreshLayout:1.0.3' 31 | compile 'com.scwang.smartrefresh:SmartRefreshHeader:1.0.3' 32 | } 33 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/wangsheng/Documents/developSoftware/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/androidTest/java/com/sheng/yanxuanrefresh/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.sheng.yanxuanrefresh; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.sheng.yanxuanrefresh", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/java/com/sheng/yanxuanrefresh/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.sheng.yanxuanrefresh; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.scwang.smartrefresh.layout.api.RefreshLayout; 7 | 8 | public class MainActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_main); 14 | RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); 15 | refreshLayout.autoRefresh(400); 16 | refreshLayout.setReboundDuration(1000); 17 | refreshLayout.setDisableContentWhenRefresh(true); 18 | refreshLayout.finishRefresh(8000); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/java/com/sheng/yanxuanrefresh/YXRefreshHeader.java: -------------------------------------------------------------------------------- 1 | package com.sheng.yanxuanrefresh; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.widget.LinearLayout; 11 | 12 | import com.scwang.smartrefresh.layout.api.RefreshHeader; 13 | import com.scwang.smartrefresh.layout.api.RefreshKernel; 14 | import com.scwang.smartrefresh.layout.api.RefreshLayout; 15 | import com.scwang.smartrefresh.layout.constant.RefreshState; 16 | import com.scwang.smartrefresh.layout.constant.SpinnerStyle; 17 | import com.sheng.yanxuanrefresh.refresh.ViewStatus; 18 | import com.sheng.yanxuanrefresh.refresh.YanXuanRefreshView; 19 | 20 | /** 21 | * Created by wangsheng on 17/8/17. 22 | */ 23 | 24 | public class YXRefreshHeader extends LinearLayout implements RefreshHeader{ 25 | 26 | private YanXuanRefreshView yanXuanRefreshView; 27 | 28 | public YXRefreshHeader(Context context) { 29 | super(context); 30 | } 31 | 32 | public YXRefreshHeader(Context context, @Nullable AttributeSet attrs) { 33 | super(context,attrs); 34 | LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 35 | View view = layoutInflater.inflate(R.layout.yx_refresh_header,this); 36 | yanXuanRefreshView = (YanXuanRefreshView) view.findViewById(R.id.refreshView); 37 | } 38 | 39 | public YXRefreshHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 40 | super(context, attrs, defStyleAttr); 41 | } 42 | 43 | @Override 44 | public void onPullingDown(float percent, int offset, int headerHeight, int extendHeight) { 45 | yanXuanRefreshView.setDistance(offset); 46 | Log.d("下拉距离",offset+""); 47 | } 48 | 49 | @Override 50 | public void onReleasing(float percent, int offset, int headerHeight, int extendHeight) { 51 | 52 | } 53 | 54 | @NonNull 55 | @Override 56 | public View getView() { 57 | return this; 58 | } 59 | 60 | @Override 61 | public SpinnerStyle getSpinnerStyle() { 62 | return SpinnerStyle.Translate; 63 | } 64 | 65 | @Override 66 | public void setPrimaryColors(int... colors) { 67 | 68 | } 69 | 70 | @Override 71 | public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { 72 | 73 | } 74 | 75 | @Override 76 | public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { 77 | 78 | } 79 | 80 | @Override 81 | public void onStartAnimator(RefreshLayout layout, int height, int extendHeight) { 82 | 83 | } 84 | 85 | @Override 86 | public int onFinish(RefreshLayout layout, boolean success) { 87 | return 0; 88 | } 89 | 90 | @Override 91 | public boolean isSupportHorizontalDrag() { 92 | return false; 93 | } 94 | 95 | @Override 96 | public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { 97 | switch (newState){ 98 | case Refreshing: 99 | yanXuanRefreshView.setViewStatus(ViewStatus.REFRESHING); 100 | break; 101 | case None: 102 | if (oldState== RefreshState.RefreshFinish) { 103 | yanXuanRefreshView.restoreView(); 104 | } 105 | break; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/java/com/sheng/yanxuanrefresh/refresh/Point.java: -------------------------------------------------------------------------------- 1 | package com.sheng.yanxuanrefresh.refresh; 2 | 3 | //点坐标的模型 包括x,y两个属性 4 | public class Point { 5 | public float pointX; 6 | public float pointY; 7 | 8 | public Point() { 9 | } 10 | 11 | public Point(float pointX, float pointY) { 12 | this.pointX = pointX; 13 | this.pointY = pointY; 14 | } 15 | 16 | public void setPointX(float pointX) { 17 | this.pointX = pointX; 18 | } 19 | 20 | public void setPointY(float pointY) { 21 | this.pointY = pointY; 22 | } 23 | } -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/java/com/sheng/yanxuanrefresh/refresh/ViewStatus.java: -------------------------------------------------------------------------------- 1 | package com.sheng.yanxuanrefresh.refresh; 2 | 3 | public enum ViewStatus { 4 | START("初始状态",0), 5 | REFRESHING("正在刷新",1), //这个状态由刷新框架设置 6 | START_SLOGAN("绘制标语",2), 7 | START_SHAKE("抖动",3); 8 | 9 | private String name; 10 | private int id; 11 | 12 | ViewStatus(String name, int id) { 13 | this.name = name; 14 | this.id = id; 15 | } 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | public void setName(String name) { 22 | this.name = name; 23 | } 24 | 25 | public int getId() { 26 | return id; 27 | } 28 | 29 | public void setId(int id) { 30 | this.id = id; 31 | } 32 | } -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/java/com/sheng/yanxuanrefresh/refresh/YanXuanRefreshView.java: -------------------------------------------------------------------------------- 1 | package com.sheng.yanxuanrefresh.refresh; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Paint; 11 | import android.graphics.Path; 12 | import android.graphics.RectF; 13 | import android.support.annotation.Nullable; 14 | import android.util.AttributeSet; 15 | import android.view.View; 16 | import android.view.animation.DecelerateInterpolator; 17 | 18 | import com.sheng.yanxuanrefresh.R; 19 | 20 | /** 21 | * @author wangsheng 1.0.0 第一版效果基本有了 但是需要调整 22 | * 23 | * todo 一些位置计算待完善 24 | * 25 | * 根据角度计算圆上某点的坐标 26 | * 圆点坐标:(x0,y0) 27 | * 半径:r 28 | * 角度:angle 29 | * 则圆上任一点为:(x1,y1) 30 | * x1 = x0 + r * cos(angle * 3.14 /180 ) 31 | * y1 = y0 + r * sin(angle * 3.14 /180 ) 32 | */ 33 | 34 | public class YanXuanRefreshView extends View { 35 | Paint paint1; 36 | //盒子上方的角的路径 37 | Path leftPath = new Path(); 38 | Path rightPath = new Path(); 39 | 40 | //盒子的两个盖子的上方的角坐标 41 | Point pointLeftOut; 42 | Point pointLeftInner; 43 | Point pointRightOut; 44 | Point pointRightInner; 45 | 46 | //盒子上方的四个角相当于四个圆心 盖子相当于围绕圆心做运动 47 | Point lCCPointOut; 48 | Point lCCPointInner; 49 | Point rCCPointOut; 50 | Point rCCPointInner; 51 | 52 | Path path1 = new Path(); 53 | Path path2 = new Path(); 54 | Path path3 = new Path(); 55 | private int viewSizeHeight; 56 | private int viewSizeWidth; 57 | 58 | //标语字的集合 59 | private Bitmap[] slogan; 60 | //字坐标的集合 61 | private Point[] points; 62 | //严选logo 63 | private Bitmap yxLogo; 64 | //阴影的矩形 65 | private RectF rectShadow; 66 | 67 | private int jumpInt; 68 | private int shakeInt; 69 | private ViewStatus viewStatus; 70 | 71 | //当前的下拉距离 72 | int distance = 0; 73 | int leftAngle = -0; 74 | int rightAngle = Math.abs(leftAngle) - 180; 75 | 76 | private int outCircleR = 100; 77 | private int innerCircleR = 70; 78 | 79 | public YanXuanRefreshView(Context context) { 80 | this(context, null); 81 | } 82 | 83 | public YanXuanRefreshView(Context context, @Nullable AttributeSet attrs) { 84 | this(context, attrs, 0); 85 | } 86 | 87 | public YanXuanRefreshView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 88 | super(context, attrs, defStyleAttr); 89 | init(); 90 | } 91 | 92 | private void init() { 93 | paint1 = new Paint(Paint.ANTI_ALIAS_FLAG); 94 | //初始所有的点 95 | pointLeftOut = new Point(0, 0); 96 | pointLeftInner = new Point(0, 0); 97 | pointRightOut = new Point(0, 0); 98 | pointRightInner = new Point(0, 0); 99 | lCCPointOut = new Point(0, 0); 100 | lCCPointInner = new Point(0, 0); 101 | rCCPointOut = new Point(0, 0); 102 | rCCPointInner = new Point(0, 0); 103 | 104 | //获取所有的字 105 | int[] arrayOfInt = {R.mipmap.all_refresh_hao_ic, R.mipmap.all_refresh_de_ic, R.mipmap.all_refresh_sheng_ic, R.mipmap.all_refresh_huo_ic 106 | , R.mipmap.all_refresh_mei_ic, R.mipmap.all_refresh_na_ic, R.mipmap.all_refresh_me_ic, R.mipmap.all_refresh_gui_ic}; 107 | slogan = new Bitmap[arrayOfInt.length]; 108 | points = new Point[arrayOfInt.length]; 109 | for (int i = 0; i < arrayOfInt.length; i++) { 110 | slogan[i] = BitmapFactory.decodeResource(getResources(), arrayOfInt[i]); 111 | points[i] = new Point(); 112 | } 113 | 114 | yxLogo = BitmapFactory.decodeResource(getResources(), R.mipmap.all_refresh_yanxuan_ic); 115 | 116 | rectShadow = new RectF(); 117 | viewStatus = ViewStatus.START; 118 | 119 | } 120 | 121 | @Override 122 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 123 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 124 | viewSizeHeight = getMeasuredHeight(); 125 | viewSizeWidth = getMeasuredWidth(); 126 | } 127 | 128 | @Override 129 | protected void onDraw(Canvas canvas) { 130 | lCCPointOut.pointX = viewSizeWidth / 2 - outCircleR; 131 | lCCPointOut.pointY = viewSizeHeight / 2; 132 | lCCPointInner.pointX = viewSizeWidth / 2 - innerCircleR; 133 | lCCPointInner.pointY = viewSizeHeight / 2 - 30; 134 | rCCPointOut.pointX = viewSizeWidth / 2 + outCircleR; 135 | rCCPointOut.pointY = viewSizeHeight / 2; 136 | rCCPointInner.pointX = viewSizeWidth / 2 + innerCircleR; 137 | rCCPointInner.pointY = viewSizeHeight / 2 - 30; 138 | 139 | pointLeftOut.setPointX((float) (lCCPointOut.pointX + outCircleR * Math.cos(leftAngle * 3.14 / 180))); 140 | pointLeftOut.setPointY((float) (lCCPointOut.pointY + outCircleR * Math.sin(leftAngle * 3.14 / 180))); 141 | 142 | pointLeftInner.setPointX((float) (lCCPointInner.pointX + innerCircleR * Math.cos(leftAngle * 3.14 / 180))); 143 | pointLeftInner.setPointY((float) (lCCPointInner.pointY + innerCircleR * Math.sin(leftAngle * 3.14 / 180))); 144 | 145 | pointRightOut.setPointX((float) (rCCPointOut.pointX + outCircleR * Math.cos(rightAngle * 3.14 / 180))); 146 | pointRightOut.setPointY((float) (rCCPointOut.pointY + outCircleR * Math.sin(rightAngle * 3.14 / 180))); 147 | 148 | pointRightInner.setPointX((float) (rCCPointInner.pointX + innerCircleR * Math.cos(rightAngle * 3.14 / 180))); 149 | pointRightInner.setPointY((float) (rCCPointInner.pointY + innerCircleR * Math.sin(rightAngle * 3.14 / 180))); 150 | 151 | paint1.setAntiAlias(true); 152 | paint1.setColor(Color.parseColor("#FF7C7C7C")); 153 | //绘制第一个梯形 154 | path1.moveTo(lCCPointInner.pointX, lCCPointInner.pointY); 155 | path1.lineTo(rCCPointInner.pointX, rCCPointInner.pointY); 156 | path1.lineTo(rCCPointOut.pointX, rCCPointOut.pointY); 157 | path1.lineTo(lCCPointOut.pointX, lCCPointOut.pointY); 158 | path1.close(); 159 | canvas.drawPath(path1, paint1); 160 | 161 | //绘制内侧矩形 162 | paint1.setColor(Color.parseColor("#FF979797")); 163 | path2.moveTo(lCCPointInner.pointX, lCCPointInner.pointY); 164 | path2.lineTo(rCCPointInner.pointX, rCCPointInner.pointY); 165 | path2.lineTo(rCCPointInner.pointX, rCCPointOut.pointY); 166 | path2.lineTo(lCCPointInner.pointX, lCCPointOut.pointY); 167 | path2.close(); 168 | canvas.drawPath(path2, paint1); 169 | 170 | drawCover(canvas); 171 | drawSlogan(canvas); 172 | 173 | //绘制阴影 174 | paint1.setColor(Color.parseColor("#FFDCDCDE")); 175 | rectShadow.left = lCCPointOut.pointX - 50; 176 | rectShadow.right = rCCPointOut.pointX + 50; 177 | rectShadow.top = lCCPointOut.pointY + 80; 178 | rectShadow.bottom = lCCPointOut.pointY + 120; 179 | canvas.drawOval(rectShadow, paint1); 180 | 181 | //绘制下方的矩形 182 | paint1.setColor(Color.parseColor("#FFC6C6C6")); 183 | path3.moveTo(lCCPointOut.pointX, lCCPointOut.pointY); 184 | path3.lineTo(rCCPointOut.pointX, rCCPointOut.pointY); 185 | path3.lineTo(rCCPointOut.pointX, rCCPointOut.pointY + 100); 186 | path3.lineTo(lCCPointOut.pointX, lCCPointOut.pointY + 100); 187 | path3.close(); 188 | canvas.drawPath(path3, paint1); 189 | paint1.reset(); 190 | 191 | //绘制严选文字logo 192 | canvas.drawBitmap(yxLogo, lCCPointOut.pointX + 100 - yxLogo.getWidth() / 2, lCCPointOut.pointY + 50 - yxLogo.getHeight() / 2, paint1); 193 | super.onDraw(canvas); 194 | } 195 | 196 | //绘制盒子的盖子 197 | private void drawCover(Canvas canvas) { 198 | //绘制左边的盖子 199 | leftPath.moveTo(lCCPointInner.pointX, lCCPointInner.pointY); 200 | leftPath.lineTo(lCCPointOut.pointX, lCCPointOut.pointY); 201 | leftPath.lineTo(pointLeftOut.pointX, pointLeftOut.pointY); 202 | leftPath.lineTo(pointLeftInner.pointX, pointLeftInner.pointY); 203 | leftPath.close(); 204 | //绘制右边的盖子 205 | rightPath.moveTo(rCCPointInner.pointX, rCCPointInner.pointY); 206 | rightPath.lineTo(rCCPointOut.pointX, rCCPointOut.pointY); 207 | rightPath.lineTo(pointRightOut.pointX, pointRightOut.pointY); 208 | rightPath.lineTo(pointRightInner.pointX, pointRightInner.pointY); 209 | rightPath.close(); 210 | paint1.setColor(Color.parseColor("#FFCDCDCE")); 211 | canvas.drawPath(leftPath, paint1); 212 | canvas.drawPath(rightPath, paint1); 213 | //path需要重置 不然会有之前的绘制图像 214 | leftPath.reset(); 215 | rightPath.reset(); 216 | } 217 | 218 | //绘制标语 219 | //// TODO: 17/8/17 关于每个字的位置 这里简单写了一下 需要做调整 220 | private void drawSlogan(Canvas canvas) { 221 | if (viewStatus == ViewStatus.START_SLOGAN || viewStatus == ViewStatus.START_SHAKE) { 222 | float x; 223 | float y; 224 | for (int i = 0; i < slogan.length; i++) { 225 | if (viewStatus == ViewStatus.START_SLOGAN) { 226 | //好的生活 227 | if (i <= 3) { 228 | x = lCCPointOut.pointX + 100 - slogan[i].getWidth() - jumpInt * (2 - i) / 4; 229 | y = lCCPointOut.pointY - jumpInt * 5 / 7; 230 | } else {//没那么贵 231 | x = lCCPointOut.pointX + 100 + jumpInt * (i - 5) / 4; 232 | y = lCCPointOut.pointY - jumpInt * 2 / 5; 233 | } 234 | points[i].setPointX(x); 235 | points[i].setPointY(y); 236 | canvas.drawBitmap(slogan[i], points[i].pointX, points[i].pointY, paint1); 237 | } else { 238 | canvas.drawBitmap(slogan[i], points[i].pointX, points[i].pointY + (i % 2 == 0 ? shakeInt : -shakeInt), paint1); 239 | } 240 | } 241 | } 242 | } 243 | 244 | 245 | //文字重盒子中弹起的动画 246 | public void startJumpAnim() { 247 | viewStatus = ViewStatus.START_SLOGAN; 248 | ValueAnimator animator = ValueAnimator.ofInt(0, 200); 249 | animator.setDuration(700); 250 | animator.setInterpolator(new DecelerateInterpolator(2f)); 251 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 252 | @Override 253 | public void onAnimationUpdate(ValueAnimator animation) { 254 | jumpInt = (int) animation.getAnimatedValue(); 255 | invalidate(); 256 | } 257 | }); 258 | animator.addListener(new Animator.AnimatorListener() { 259 | @Override 260 | public void onAnimationEnd(Animator animation) { 261 | viewStatus = ViewStatus.START_SHAKE; 262 | //开始抖动文字 263 | shakeSlogan(); 264 | } 265 | 266 | @Override 267 | public void onAnimationStart(Animator animation) { 268 | 269 | } 270 | 271 | @Override 272 | public void onAnimationCancel(Animator animation) { 273 | 274 | } 275 | 276 | @Override 277 | public void onAnimationRepeat(Animator animation) { 278 | 279 | } 280 | }); 281 | animator.start(); 282 | } 283 | 284 | //标语抖动动画 285 | public void shakeSlogan() { 286 | //简单实现的抖动动画 想要实现更好的效果可以查看https://github.com/hujiaweibujidao/wava 287 | ValueAnimator animator = ValueAnimator.ofInt(-2, 2); 288 | animator.setRepeatCount(-1); 289 | animator.setDuration(200); 290 | animator.setRepeatMode(ValueAnimator.REVERSE); 291 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 292 | @Override 293 | public void onAnimationUpdate(ValueAnimator animation) { 294 | shakeInt = (int) animation.getAnimatedValue(); 295 | invalidate(); 296 | } 297 | }); 298 | animator.start(); 299 | } 300 | 301 | //设置偏移 这里为了方便计算 设置210偏移对应盖子打开的最大角度 +-210 302 | public void setDistance(int distance) { 303 | this.distance = distance; 304 | int animOffset = distance - viewSizeHeight/2-30; 305 | if (viewStatus == ViewStatus.START && animOffset > 0) { 306 | if (leftAngle >= -210) { 307 | //规避原则 防止下拉速度过快 distance并不是以+1的形式递增 而是一下子加很多 造成画图不准 308 | if (animOffset >= 210) { 309 | animOffset = 210; 310 | } 311 | leftAngle = -animOffset; 312 | rightAngle = Math.abs(leftAngle) - 180; 313 | invalidate(); 314 | } 315 | }else if(viewStatus == ViewStatus.START&&animOffset<=0){ 316 | restoreView(); 317 | } 318 | } 319 | 320 | //设置是否正在刷新 321 | public void setViewStatus(ViewStatus viewStatus) { 322 | this.viewStatus = viewStatus; 323 | if (viewStatus == ViewStatus.REFRESHING) { 324 | //可能下拉没有到210 就开始刷新了 看具体xml中的height // TODO: 17/8/18 需要调整完善的地方 325 | leftAngle = -210; 326 | rightAngle = 30; 327 | invalidate(); 328 | startJumpAnim(); 329 | } 330 | } 331 | 332 | //刷新完毕 重置view的状态 333 | public void restoreView() { 334 | viewStatus = ViewStatus.START; 335 | leftAngle = 0; 336 | rightAngle = -180; 337 | distance = 0; 338 | invalidate(); 339 | } 340 | 341 | } 342 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 14 | 15 | 22 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/layout/yx_refresh_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_de_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_de_ic.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_gui_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_gui_ic.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_hao_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_hao_ic.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_huo_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_huo_ic.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_me_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_me_ic.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_mei_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_mei_ic.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_na_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_na_ic.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_sheng_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_sheng_ic.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_yanxuan_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/all_refresh_yanxuan_ic.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/YanXuanRefresh/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | YanXuanRefresh 3 | 4 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /YanXuanRefresh/app/src/test/java/com/sheng/yanxuanrefresh/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.sheng.yanxuanrefresh; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /YanXuanRefresh/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.2' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /refresh.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccdada/YanXuanRefresh/521cc5d3ca50d658c7cf7d6c8b16d0a303c37d00/refresh.gif --------------------------------------------------------------------------------