├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── think.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ivy │ │ └── weather │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── ivy │ │ │ ├── bomb │ │ │ ├── BombActivity.java │ │ │ └── BombView.java │ │ │ ├── jelly │ │ │ ├── JellyView.java │ │ │ └── MainJellyActivity.java │ │ │ ├── main │ │ │ ├── IvyOnTouchListener.java │ │ │ ├── MainActivity.java │ │ │ └── ViewItem.java │ │ │ ├── ruler │ │ │ ├── RulerActivity.java │ │ │ └── ScrollerRulerView.java │ │ │ ├── setting │ │ │ └── SettingActivity.java │ │ │ └── weather │ │ │ ├── CircleInfo.java │ │ │ ├── CircleTypeEvaluator.java │ │ │ ├── WeatherActivity.java │ │ │ └── WeatherView.java │ └── res │ │ ├── drawable │ │ └── a.gif │ │ ├── layout │ │ ├── activity_bomb.xml │ │ ├── activity_jelly_main.xml │ │ ├── activity_main.xml │ │ ├── activity_ruler.xml │ │ ├── activity_setting.xml │ │ ├── activity_weather.xml │ │ └── adapter_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── shortcuts.xml │ └── test │ └── java │ └── com │ └── ivy │ └── weather │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dictionaries/think.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 26 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Abstraction issuesJava 46 | 47 | 48 | Android Lint 49 | 50 | 51 | Assignment issuesGroovy 52 | 53 | 54 | Assignment issuesJava 55 | 56 | 57 | Bitwise operation issuesJava 58 | 59 | 60 | Class structureJava 61 | 62 | 63 | Code style issuesJava 64 | 65 | 66 | Control FlowGroovy 67 | 68 | 69 | Control flow issuesJava 70 | 71 | 72 | GPath inspectionsGroovy 73 | 74 | 75 | General 76 | 77 | 78 | GeneralJava 79 | 80 | 81 | Groovy 82 | 83 | 84 | HTML 85 | 86 | 87 | Initialization issuesJava 88 | 89 | 90 | Internationalization issues 91 | 92 | 93 | Internationalization issuesJava 94 | 95 | 96 | J2ME issuesJava 97 | 98 | 99 | JUnit issues 100 | 101 | 102 | JUnit issuesJava 103 | 104 | 105 | Java 106 | 107 | 108 | Java language level issuesJava 109 | 110 | 111 | Java language level migration aidsJava 112 | 113 | 114 | Javadoc issuesJava 115 | 116 | 117 | Logging issuesJava 118 | 119 | 120 | Memory issuesJava 121 | 122 | 123 | Naming ConventionsGroovy 124 | 125 | 126 | Naming conventionsJava 127 | 128 | 129 | Numeric issuesJava 130 | 131 | 132 | Performance issuesJava 133 | 134 | 135 | Portability issuesJava 136 | 137 | 138 | Potentially confusing code constructsGroovy 139 | 140 | 141 | Probable bugsGroovy 142 | 143 | 144 | Probable bugsJava 145 | 146 | 147 | Properties Files 148 | 149 | 150 | RELAX NG 151 | 152 | 153 | Security issuesJava 154 | 155 | 156 | TestNG 157 | 158 | 159 | Threading issuesJava 160 | 161 | 162 | Visibility issuesJava 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 184 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 1. Weather view,[详细请查看文章](http://www.jianshu.com/p/2c9dc35f3aad) 3 | 4 | ![图片](http://upload-images.jianshu.io/upload_images/837800-2f51695590a5c82b.gif?imageMogr2/auto-orient/strip) 5 | 6 | 2. ScrollRuleView 依照薄荷健康的滑动卷尺效果 7 | 8 | ![图片](https://github.com/fengivy/Weather/blob/master/app/src/main/res/drawable/a.gif) 9 | 10 | 11 | 3.bombView小炸弹,[详细请查看文章](http://www.jianshu.com/p/a622fa556c1b) 12 | 13 | ![图片](http://upload-images.jianshu.io/upload_images/837800-3301776994029a39.gif?imageMogr2/auto-orient/strip) 14 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /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.ivy.weather" 8 | minSdkVersion 14 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(include: ['*.jar'], dir: 'libs') 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 | testCompile 'junit:junit:4.12' 29 | compile 'com.android.support:recyclerview-v7:25.3.1' 30 | } 31 | -------------------------------------------------------------------------------- /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 D:\Android\SDK/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ivy/weather/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.ivy.weather; 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.ivy.weather", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/bomb/BombActivity.java: -------------------------------------------------------------------------------- 1 | package com.ivy.bomb; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | import com.ivy.weather.R; 9 | 10 | /** 11 | * Created by ivy on 2017/10/25. 12 | * Description: 13 | */ 14 | 15 | public class BombActivity extends AppCompatActivity { 16 | @Override 17 | protected void onCreate(@Nullable Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_bomb); 20 | final BombView bombView= (BombView) this.findViewById(R.id.bomb_view); 21 | bombView.setOnClickListener(new View.OnClickListener() { 22 | @Override 23 | public void onClick(View v) { 24 | bombView.startAnim(); 25 | } 26 | }); 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/bomb/BombView.java: -------------------------------------------------------------------------------- 1 | package com.ivy.bomb; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ValueAnimator; 7 | import android.content.Context; 8 | import android.graphics.Camera; 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.DashPathEffect; 12 | import android.graphics.Matrix; 13 | import android.graphics.Paint; 14 | import android.graphics.Path; 15 | import android.graphics.PathMeasure; 16 | import android.graphics.Point; 17 | import android.graphics.PorterDuff; 18 | import android.graphics.PorterDuffXfermode; 19 | import android.graphics.RectF; 20 | import android.os.Build; 21 | import android.support.annotation.Nullable; 22 | import android.support.annotation.RequiresApi; 23 | import android.util.AttributeSet; 24 | import android.util.TypedValue; 25 | import android.view.View; 26 | import android.view.animation.DecelerateInterpolator; 27 | import static android.animation.ValueAnimator.ofFloat; 28 | 29 | /** 30 | * Created by ivy on 2017/10/24. 31 | * Description: 32 | */ 33 | 34 | public class BombView extends View { 35 | private Paint mPaint; 36 | private Path mPath, mHeadLinePath ,mHeadPath ,mBodyLightPath; 37 | private int bombColor = Color.parseColor("#88B0E8"); 38 | private int bombLineColor = Color.parseColor("#181D82"); 39 | private int bombShadowColor = Color.parseColor("#77609ee6"); 40 | private int lightColor = Color.WHITE; 41 | private int bombLineWidth; 42 | //炸弹身体半径 高光半径 43 | private int bodyRadius, highLightRadius; 44 | //用于处理线条的分割点 45 | private DashPathEffect groundDashPathEffect,bodyDashPathEffect,highLightPathEffect,mHeadEffect; 46 | private RectF mRectF; 47 | private PathMeasure mPathMeasure=new PathMeasure(); 48 | private float[] mPathPosition=new float[2]; 49 | //动画控制相关 50 | private float faceLROffset , faceMaxLROffset; 51 | private float faceTBOffset =0, faceMaxTBOffset; 52 | private float bombLRRotate =15, bombMaxLRRotate =15 ,bombTBRotate=0, bombMaxTBRotate =5; 53 | private float eyeRadius,eyeMaxRadius,eyeMinRadius; 54 | private float headLinePercent=1,headLineLightRate=0; 55 | private float maxBlastCircleRadius, currentBlastCircleRadius,blastCircleRadiusPercent; 56 | //用于计算mouth第二阶段变化 57 | private float mouthMaxWidthOffset, mouthMaxHeightOffset, mouthWidthOffset=0, mouthHeightOffset =0,mouthOffsetPercent=0; 58 | //用于计算mouth第一阶段变化 59 | private float mouthFSMaxWidthOffset,mouthFSMaxHeightOffset,mouthFSWidthOffset=0,mouthFSHeightOffset=0; 60 | //炸弹的中心点 61 | private int bombCenterX,bombCenterY; 62 | //用于控制旋转 63 | private Camera mCamera=new Camera(); 64 | private Matrix mMatrix=new Matrix(); 65 | 66 | 67 | public BombView(Context context) { 68 | super(context); 69 | init(); 70 | } 71 | 72 | public BombView(Context context, @Nullable AttributeSet attrs) { 73 | super(context, attrs); 74 | init(); 75 | } 76 | 77 | public BombView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 78 | super(context, attrs, defStyleAttr); 79 | init(); 80 | } 81 | 82 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 83 | public BombView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 84 | super(context, attrs, defStyleAttr, defStyleRes); 85 | init(); 86 | } 87 | 88 | @Override 89 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 90 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 91 | int width=getMeasuredWidth(); 92 | int height=getMeasuredHeight(); 93 | int minLength= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,100,getContext().getResources().getDisplayMetrics()); 94 | minLength=Math.max(Math.min(width,height),minLength); 95 | setMeasuredDimension(minLength,minLength); 96 | } 97 | 98 | private void init() { 99 | mPaint=new Paint(); 100 | mPaint.setAntiAlias(true); 101 | mPaint.setDither(true); 102 | mPaint.setStrokeCap(Paint.Cap.ROUND); 103 | mPaint.setStrokeJoin(Paint.Join.ROUND); 104 | mPath=new Path(); 105 | mRectF=new RectF(); 106 | mHeadLinePath =new Path(); 107 | mHeadPath =new Path(); 108 | mBodyLightPath =new Path(); 109 | } 110 | 111 | @Override 112 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 113 | super.onSizeChanged(w, h, oldw, oldh); 114 | bombLineWidth =getMeasuredWidth()/35; 115 | mPaint.setStrokeWidth(bombLineWidth); 116 | float[] groundEffectFloat=new float[]{bombLineWidth/4,bombLineWidth/2+bombLineWidth,bombLineWidth*2,bombLineWidth/3*2+bombLineWidth,getMeasuredWidth(),0}; 117 | groundDashPathEffect=new DashPathEffect(groundEffectFloat,0); 118 | bodyRadius= (int) (getMeasuredHeight()/3.4f); 119 | float[] bodyEffectFloat=new float[]{getRadianLength(56,bodyRadius) 120 | ,getRadianLength(4,bodyRadius)+bombLineWidth 121 | ,getRadianLength(2.5f,bodyRadius) 122 | ,getRadianLength(4,bodyRadius)+bombLineWidth 123 | ,getRadianLength(220,bodyRadius) 124 | ,getRadianLength(12,bodyRadius)+bombLineWidth 125 | ,getRadianLength(90,bodyRadius) 126 | ,0}; 127 | bodyDashPathEffect=new DashPathEffect(bodyEffectFloat,0); 128 | 129 | highLightRadius =bodyRadius/3*2; 130 | float[] highLightFloat=new float[]{0,getRadianLength(95, highLightRadius) 131 | ,getRadianLength(0.5f, highLightRadius) 132 | ,getRadianLength(5, highLightRadius)+bombLineWidth 133 | ,getRadianLength(12, highLightRadius) 134 | ,getRadianLength(5, highLightRadius)+bombLineWidth 135 | ,getRadianLength(24, highLightRadius) 136 | ,getRadianLength(270, highLightRadius)}; 137 | highLightPathEffect=new DashPathEffect(highLightFloat,0); 138 | 139 | float padding= (float) (2*bombLineWidth*Math.PI/2/72); 140 | mHeadEffect=new DashPathEffect(new float[]{padding*2,padding},0); 141 | 142 | faceMaxLROffset =bodyRadius/3; 143 | faceLROffset =-faceMaxLROffset; 144 | eyeRadius=eyeMaxRadius=bombLineWidth/2; 145 | eyeMinRadius=eyeMaxRadius/6; 146 | 147 | faceMaxTBOffset=bodyRadius/3; 148 | maxBlastCircleRadius = getMeasuredWidth()*(2); 149 | 150 | mouthMaxWidthOffset =bodyRadius/5-bodyRadius/5/10; 151 | mouthMaxHeightOffset =bodyRadius/5/2; 152 | 153 | mouthFSMaxWidthOffset= mouthMaxWidthOffset/3; 154 | mouthFSMaxHeightOffset = mouthMaxHeightOffset/2; 155 | 156 | bombCenterX=getMeasuredWidth()/2; 157 | bombCenterY=getMeasuredHeight()-bombLineWidth-bodyRadius; 158 | 159 | setHeadLinePath(); 160 | setHeadPath(); 161 | setBodyLightPath(); 162 | } 163 | 164 | private void setBodyLightPath() { 165 | //160度坐标计算 166 | mBodyLightPath.reset(); 167 | Point point=getPointInCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth,160); 168 | mBodyLightPath.moveTo(point.x-bodyRadius,point.y); 169 | mBodyLightPath.lineTo(point.x,point.y); 170 | Point pointControl=getPointInCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth+bombLineWidth*2.2f,210); 171 | point=getPointInCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth,260); 172 | mBodyLightPath.quadTo(pointControl.x,pointControl.y,point.x,point.y); 173 | mBodyLightPath.lineTo(point.x-bodyRadius,point.y); 174 | mBodyLightPath.close(); 175 | } 176 | 177 | private void setHeadPath() { 178 | mHeadPath.reset(); 179 | mHeadPath.moveTo(bombCenterX-bodyRadius/5,getMeasuredHeight()/2); 180 | mHeadPath.lineTo(bombCenterX+bodyRadius/5,getMeasuredHeight()/2); 181 | mHeadPath.lineTo(bombCenterX+bodyRadius/5,bombCenterY-bodyRadius-bodyRadius/4); 182 | mHeadPath.lineTo(bombCenterX,bombCenterY-bodyRadius-bodyRadius/4-bodyRadius/4/4); 183 | mHeadPath.lineTo(bombCenterX-bodyRadius/5,bombCenterY-bodyRadius-bodyRadius/4); 184 | mHeadPath.close(); 185 | } 186 | 187 | private void setHeadLinePath() { 188 | float beginY=bombCenterY-bodyRadius-bodyRadius/4-bodyRadius/4/4; 189 | mHeadLinePath.reset(); 190 | mHeadLinePath.moveTo(bombCenterX,beginY); 191 | float controlY=beginY-bodyRadius/2.2f; 192 | float bottomPointY=bombCenterY-bodyRadius;//转折点高度 193 | mHeadLinePath.quadTo(bombCenterX+bodyRadius/2/5,controlY,bombCenterX+bodyRadius/2,controlY); 194 | mHeadLinePath.cubicTo(bombCenterX+bodyRadius/2+bodyRadius/2/5*4,controlY 195 | ,bombCenterX+bodyRadius/2*2f-bodyRadius/16,bottomPointY-bodyRadius/6 196 | ,bombCenterX+bodyRadius/2*2f,bottomPointY); 197 | mHeadLinePath.quadTo(bombCenterX+bodyRadius/2*2f+bodyRadius/16,bottomPointY+bodyRadius/6 198 | ,bombCenterX+bodyRadius/2*2f+bodyRadius/7,bottomPointY+bodyRadius/4); 199 | } 200 | 201 | @Override 202 | protected void onDraw(Canvas canvas) { 203 | super.onDraw(canvas); 204 | drawHead(canvas);//头部小房子 205 | drawGround(canvas);//地板 206 | drawBody(canvas);//身体 207 | drawBodyBorder(canvas);//身体边框 208 | drawFace(canvas);//脸 209 | drawFaceShadow(canvas);//脸阴影 210 | drawHeadLine(canvas);//头部的炸弹线 211 | drawBlast(canvas);//爆炸 212 | } 213 | 214 | private void drawFaceShadow(Canvas canvas) { 215 | //两个圆相交产生阴影 216 | int save=canvas.saveLayer(0,0,getMeasuredWidth(),getMeasuredHeight(),null,Canvas.ALL_SAVE_FLAG); 217 | mPaint.setStyle(Paint.Style.FILL); 218 | mPaint.setColor(bombShadowColor); 219 | canvas.drawCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth/2,mPaint); 220 | mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 221 | canvas.translate(-bodyRadius/5,-bodyRadius/5); 222 | mPaint.setColor(bombColor); 223 | canvas.drawCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth/2,mPaint); 224 | canvas.restoreToCount(save); 225 | mPaint.setXfermode(null); 226 | } 227 | 228 | private void drawFace(Canvas canvas) { 229 | canvas.save(); 230 | mCamera.save(); 231 | mCamera.rotate(bombTBRotate,0,-bombLRRotate/3); 232 | mMatrix.reset(); 233 | mCamera.getMatrix(mMatrix); 234 | mCamera.restore(); 235 | mMatrix.preTranslate(-bombCenterX,-(bombCenterY)); 236 | mMatrix.postTranslate(bombCenterX,bombCenterY); 237 | mMatrix.postTranslate(faceLROffset,faceTBOffset); 238 | canvas.concat(mMatrix); 239 | //眼睛 椭圆控制 240 | mPaint.setStyle(Paint.Style.FILL); 241 | mPaint.setColor(bombLineColor); 242 | float eyeY=bombCenterY+bodyRadius/5; 243 | float eyeWidth=Math.max(eyeMaxRadius,eyeRadius); 244 | mRectF.set(bombCenterX-bodyRadius/3.5f-eyeWidth,eyeY-eyeRadius 245 | ,bombCenterX-bodyRadius/3.5f+eyeWidth,eyeY+eyeRadius); 246 | canvas.drawOval(mRectF,mPaint); 247 | mRectF.set(bombCenterX+bodyRadius/3.5f-eyeWidth,eyeY-eyeRadius 248 | ,bombCenterX+bodyRadius/3.5f+eyeWidth,eyeY+eyeRadius); 249 | canvas.drawOval(mRectF,mPaint); 250 | //画嘴巴 路径 251 | float mouthY=eyeY+bombLineWidth- mouthHeightOffset;//嘴巴起始高度 252 | float mouthMaxY=mouthY+bodyRadius/7+ mouthHeightOffset - mouthFSHeightOffset;//嘴巴最底部 253 | float mouthHalfDistance=bodyRadius/5-mouthWidthOffset*0.5f + mouthFSWidthOffset;//嘴巴顶部的拐角的一半宽度 254 | float mouthTopHalfDistance=(mouthHalfDistance-bodyRadius/5/10)-mouthWidthOffset; //嘴巴顶部的一半宽度 255 | float mouthHorDistanceHalf=(mouthMaxY-mouthY)/(6-4*mouthOffsetPercent);//嘴角控制点的距离嘴角点的竖直距离 256 | if (mouthTopHalfDistance0.65) { 312 | mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 313 | canvas.drawCircle(bombCenterX, circleY, currentBlastCircleRadius - maxBlastCircleRadius * 0.65f, mPaint); 314 | mPaint.setXfermode(null); 315 | } 316 | canvas.restoreToCount(save); 317 | } 318 | 319 | private void drawHeadLine(Canvas canvas) { 320 | canvas.save(); 321 | mCamera.save(); 322 | mCamera.rotate(bombTBRotate,0,-bombLRRotate); 323 | mMatrix.reset(); 324 | mCamera.getMatrix(mMatrix); 325 | mCamera.restore(); 326 | mMatrix.preTranslate(-bombCenterX,-bombCenterY); 327 | mMatrix.postTranslate(bombCenterX,bombCenterY); 328 | canvas.concat(mMatrix); 329 | mPaint.setColor(bombLineColor); 330 | mPaint.setStyle(Paint.Style.STROKE); 331 | mPathMeasure.setPath(mHeadLinePath,false); 332 | mPath.reset(); 333 | mPathMeasure.getSegment(0,mPathMeasure.getLength()*headLinePercent,mPath,true); 334 | canvas.drawPath(mPath,mPaint); 335 | //火光 336 | mPathMeasure.setPath(mPath,false); 337 | float length=mPathMeasure.getLength(); 338 | mPathMeasure.getPosTan(length,mPathPosition,null); 339 | mPaint.setColor(Color.parseColor("#fbb42d")); 340 | mPaint.setStyle(Paint.Style.FILL); 341 | canvas.drawCircle(mPathPosition[0],mPathPosition[1],Math.max(bombLineWidth/1.3f*headLineLightRate,bombLineWidth/2*1.2f),mPaint); 342 | mPaint.setColor(Color.parseColor("#f34671")); 343 | mPath.reset(); 344 | mPath.addCircle(mPathPosition[0],mPathPosition[1],bombLineWidth/2.5f*headLineLightRate, Path.Direction.CCW); 345 | mPaint.setPathEffect(mHeadEffect); 346 | canvas.drawPath(mPath,mPaint); 347 | mPaint.setPathEffect(null); 348 | mPaint.setColor(Color.WHITE); 349 | canvas.drawCircle(mPathPosition[0],mPathPosition[1], bombLineWidth /6.5f * headLineLightRate,mPaint); 350 | canvas.restore(); 351 | } 352 | 353 | private void drawHead(Canvas canvas) { 354 | canvas.save(); 355 | mCamera.save(); 356 | mCamera.rotate(bombTBRotate,0,-bombLRRotate); 357 | mMatrix.reset(); 358 | mCamera.getMatrix(mMatrix); 359 | mCamera.restore(); 360 | mMatrix.preTranslate(-bombCenterX,-(bombCenterY)); 361 | mMatrix.postTranslate(bombCenterX,(bombCenterY)); 362 | canvas.concat(mMatrix); 363 | 364 | mPaint.setStrokeWidth(bombLineWidth*0.8f); 365 | //内部 366 | mPaint.setColor(bombColor); 367 | mPaint.setStyle(Paint.Style.FILL); 368 | canvas.drawPath(mHeadPath,mPaint); 369 | //边框 370 | mPaint.setColor(bombLineColor); 371 | mPaint.setStyle(Paint.Style.STROKE); 372 | canvas.drawPath(mHeadPath,mPaint); 373 | mPaint.setStrokeWidth(bombLineWidth); 374 | canvas.restore(); 375 | } 376 | 377 | private void drawBody(Canvas canvas) { 378 | mPaint.setStyle(Paint.Style.FILL); 379 | mPaint.setColor(bombColor); 380 | canvas.drawCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth/2,mPaint); 381 | //左上角光点 382 | mPaint.setPathEffect(highLightPathEffect); 383 | mPaint.setColor(lightColor); 384 | mPaint.setStyle(Paint.Style.STROKE); 385 | mPath.reset(); 386 | mPath.addCircle(bombCenterX,bombCenterY, highLightRadius, Path.Direction.CCW); 387 | canvas.drawPath(mPath,mPaint); 388 | //左上角的光边 389 | mPaint.setPathEffect(null); 390 | mRectF.set(bombCenterX-bodyRadius+bombLineWidth/2,bombCenterY-bodyRadius+bombLineWidth/2 391 | ,bombCenterX+bodyRadius-bombLineWidth/2,getMeasuredHeight()-bombLineWidth-bombLineWidth/2); 392 | canvas.drawArc(mRectF,160,100,false,mPaint); 393 | //拼接光边 394 | mPath.reset(); 395 | mPath.addCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth/2, Path.Direction.CCW); 396 | canvas.save(); 397 | canvas.clipPath(mPath);//裁剪圆内 398 | 399 | mPaint.setStyle(Paint.Style.FILL); 400 | mPaint.setColor(lightColor); 401 | canvas.drawPath(mBodyLightPath,mPaint); 402 | canvas.restore(); 403 | } 404 | 405 | 406 | private Point getPointInCircle(int circleX,int circleY,float radius,float angle){ 407 | Point mPoint=new Point(); 408 | mPoint.set((int)(circleX + radius * Math.cos(Math.toRadians(angle))), 409 | (int)(circleY+radius*Math.sin(Math.toRadians(angle)))); 410 | return mPoint; 411 | } 412 | 413 | private void drawBodyBorder(Canvas canvas) { 414 | canvas.save(); 415 | canvas.rotate(bombLRRotate,bombCenterX,bombCenterY); 416 | mPaint.setPathEffect(bodyDashPathEffect); 417 | mPaint.setColor(bombLineColor); 418 | mPaint.setStyle(Paint.Style.STROKE); 419 | mPath.reset(); 420 | mPath.addCircle(bombCenterX,bombCenterY,bodyRadius, Path.Direction.CW); 421 | canvas.drawPath(mPath,mPaint); 422 | canvas.restore(); 423 | } 424 | 425 | private void drawGround(Canvas canvas) { 426 | mPaint.setStyle(Paint.Style.STROKE); 427 | mPaint.setColor(bombLineColor); 428 | mPaint.setPathEffect(groundDashPathEffect); 429 | mPath.reset(); 430 | mPath.moveTo(bombLineWidth/2,getMeasuredHeight()-bombLineWidth/2); 431 | mPath.lineTo(getMeasuredWidth()-bombLineWidth/2,getMeasuredHeight()-bombLineWidth/2); 432 | canvas.drawPath(mPath,mPaint); 433 | } 434 | 435 | private AnimatorSet set=new AnimatorSet(); 436 | public void startAnim(){ 437 | stopAnim(); 438 | ValueAnimator faceTBAnim=getFaceTopBottomAnim(); 439 | ValueAnimator faceLRAnim=getFaceLeftRightAnim(); 440 | AnimatorSet faceChangeAnim=getFaceChangeAnim(); 441 | set.play(getBlastAnim()).after(faceChangeAnim); 442 | set.play(faceChangeAnim).after(faceTBAnim); 443 | set.play(faceTBAnim).after(faceLRAnim); 444 | set.play(faceLRAnim).with(getHeadLineAnim()); 445 | set.start(); 446 | } 447 | 448 | public void stopAnim(){ 449 | set.cancel(); 450 | initData(); 451 | invalidate(); 452 | } 453 | 454 | private void initData() { 455 | faceTBOffset=0; 456 | bombTBRotate=0; 457 | bombLRRotate=15; 458 | faceLROffset=-faceMaxLROffset; 459 | currentBlastCircleRadius =0; 460 | blastCircleRadiusPercent=0; 461 | eyeRadius=eyeMaxRadius; 462 | headLinePercent=1; 463 | mouthWidthOffset=0; 464 | mouthHeightOffset =0; 465 | mouthOffsetPercent=0; 466 | mouthFSWidthOffset=0; 467 | mouthFSHeightOffset=0; 468 | headLineLightRate=0; 469 | } 470 | 471 | private int getFaceChangeAnimTime(){ 472 | return 450; 473 | } 474 | 475 | private AnimatorSet getFaceChangeAnim(){ 476 | AnimatorSet animatorSet=new AnimatorSet(); 477 | //眼睛 478 | ValueAnimator valueAnimator= ValueAnimator.ofFloat(1,0,1.4f) 479 | .setDuration(getFaceChangeAnimTime()); 480 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 481 | @Override 482 | public void onAnimationUpdate(ValueAnimator animation) { 483 | eyeRadius=eyeMaxRadius*(float) animation.getAnimatedValue(); 484 | if (eyeRadius0.75){//第一阶段还是第二阶段 498 | mouthWidthOffset=mouthMaxWidthOffset*value; 499 | mouthHeightOffset = mouthMaxHeightOffset *value; 500 | mouthOffsetPercent=animation.getAnimatedFraction(); 501 | }else{ 502 | mouthFSWidthOffset=mouthFSMaxWidthOffset*value; 503 | mouthFSHeightOffset=mouthFSMaxHeightOffset*value; 504 | } 505 | invalidate(); 506 | } 507 | }); 508 | animatorSet.play(valueAnimator).with(mouthAnimator); 509 | return animatorSet; 510 | } 511 | 512 | private int getFaceTopBottomAnimTime(){ 513 | return 300; 514 | } 515 | private int getFaceTopBottomAnimDelayTime(){ 516 | return 200; 517 | } 518 | 519 | private ValueAnimator getFaceTopBottomAnim(){ 520 | ValueAnimator objectAnimator= ofFloat(0,1) 521 | .setDuration(getFaceTopBottomAnimTime()); 522 | objectAnimator.setStartDelay(getFaceTopBottomAnimDelayTime()); 523 | objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 524 | @Override 525 | public void onAnimationUpdate(ValueAnimator animation) { 526 | float value= (float) animation.getAnimatedValue(); 527 | faceTBOffset= -faceMaxTBOffset*value; 528 | bombTBRotate= bombMaxTBRotate*value; 529 | invalidate(); 530 | } 531 | }); 532 | return objectAnimator; 533 | } 534 | 535 | private int getFaceLeftRightAnimTime(){ 536 | return 1400; 537 | } 538 | private ValueAnimator getFaceLeftRightAnim(){ 539 | ValueAnimator valueAnimator= ofFloat(-1,0,1,0) 540 | .setDuration(getFaceLeftRightAnimTime()); 541 | valueAnimator.setInterpolator(new DecelerateInterpolator()); 542 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 543 | @Override 544 | public void onAnimationUpdate(ValueAnimator animation) { 545 | float value= (float) animation.getAnimatedValue(); 546 | faceLROffset = faceMaxLROffset *value; 547 | bombLRRotate = -bombMaxLRRotate *value; 548 | if (Math.abs(value)<0.3&&animation.getAnimatedFraction()<0.6){ 549 | eyeRadius=Math.max(eyeMaxRadius*Math.abs(value),eyeMinRadius); 550 | }else{ 551 | eyeRadius=eyeMaxRadius; 552 | } 553 | invalidate(); 554 | } 555 | }); 556 | return valueAnimator; 557 | } 558 | 559 | private int getHeadLineAnimTime(){ 560 | return getFaceLeftRightAnimTime()+getFaceTopBottomAnimTime()+getFaceTopBottomAnimDelayTime()+getFaceChangeAnimTime()+500; 561 | } 562 | private AnimatorSet getHeadLineAnim(){ 563 | AnimatorSet animatorSet=new AnimatorSet(); 564 | ValueAnimator valueAnimator= ofFloat(1f,0f) 565 | .setDuration(getHeadLineAnimTime()); 566 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 567 | @Override 568 | public void onAnimationUpdate(ValueAnimator animation) { 569 | headLinePercent= (float) animation.getAnimatedValue(); 570 | invalidate(); 571 | } 572 | }); 573 | ValueAnimator valueAnimatorLight=ValueAnimator.ofFloat(0f,1.3f,0f) 574 | .setDuration(getHeadLineAnimTime()/5); 575 | valueAnimatorLight.setRepeatCount(5); 576 | valueAnimatorLight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 577 | @Override 578 | public void onAnimationUpdate(ValueAnimator animation) { 579 | headLineLightRate= (float) animation.getAnimatedValue(); 580 | } 581 | }); 582 | animatorSet.play(valueAnimator).with(valueAnimatorLight); 583 | return animatorSet; 584 | } 585 | 586 | private ValueAnimator getBlastAnim(){ 587 | ValueAnimator valueAnimator= ofFloat(0,maxBlastCircleRadius) 588 | .setDuration(500); 589 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 590 | @Override 591 | public void onAnimationUpdate(ValueAnimator animation) { 592 | if (animation.getAnimatedFraction()>0.7f){ 593 | initData(); 594 | } 595 | currentBlastCircleRadius = (float) animation.getAnimatedValue(); 596 | blastCircleRadiusPercent=animation.getAnimatedFraction(); 597 | invalidate(); 598 | } 599 | }); 600 | valueAnimator.addListener(new AnimatorListenerAdapter() { 601 | @Override 602 | public void onAnimationEnd(Animator animation) { 603 | super.onAnimationEnd(animation); 604 | initData(); 605 | invalidate(); 606 | } 607 | }); 608 | return valueAnimator; 609 | } 610 | 611 | private float getRadianLength(float angle,float radius){ 612 | return (float) (angle*Math.PI*radius/180f); 613 | } 614 | } 615 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/jelly/JellyView.java: -------------------------------------------------------------------------------- 1 | package com.ivy.jelly; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.os.Build; 9 | import android.support.annotation.Nullable; 10 | import android.support.annotation.RequiresApi; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | 14 | /** 15 | * Created by ivy on 2017/9/19. 16 | * Description: 17 | */ 18 | 19 | public class JellyView extends View { 20 | private Paint mPaint=new Paint(); 21 | private Path mPath=new Path(); 22 | private float centerX,centerY; 23 | private float width, height; 24 | public JellyView(Context context) { 25 | super(context); 26 | init(); 27 | } 28 | 29 | public JellyView(Context context, @Nullable AttributeSet attrs) { 30 | super(context, attrs); 31 | init(); 32 | } 33 | 34 | public JellyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 35 | super(context, attrs, defStyleAttr); 36 | init(); 37 | } 38 | 39 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 40 | public JellyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 41 | super(context, attrs, defStyleAttr, defStyleRes); 42 | init(); 43 | } 44 | 45 | @Override 46 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 47 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 48 | centerX=getMeasuredWidth()/2; 49 | centerY=getMeasuredHeight()/2; 50 | width=getMeasuredWidth(); 51 | height =getMeasuredHeight(); 52 | } 53 | 54 | @Override 55 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 56 | super.onSizeChanged(w, h, oldw, oldh); 57 | centerX=w/2; 58 | centerY=h/2; 59 | width=w; 60 | height=h; 61 | } 62 | 63 | public void init(){ 64 | setBackgroundColor(Color.parseColor("#12092c")); 65 | mPaint.setColor(Color.parseColor("#00bbff")); 66 | mPaint.setAntiAlias(true); 67 | mPaint.setStyle(Paint.Style.FILL); 68 | } 69 | 70 | @Override 71 | protected void onDraw(Canvas canvas) { 72 | super.onDraw(canvas); 73 | drawHand(canvas); 74 | } 75 | 76 | private void drawHand(Canvas canvas) { 77 | mPath.reset(); 78 | /*mPath.moveTo(centerX-getMeasuredWidth()/10,centerY); 79 | mPath.quadTo(centerX,centerY-getMeasuredWidth()/10,centerX+getMeasuredWidth()/10,centerY);*/ 80 | mPath.moveTo(centerX,centerY); 81 | mPath.quadTo(centerX-width/20,centerY-width/20,centerX-width/18,centerY-width/20); 82 | mPath.close(); 83 | canvas.drawPath(mPath,mPaint); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/jelly/MainJellyActivity.java: -------------------------------------------------------------------------------- 1 | package com.ivy.jelly; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import com.ivy.weather.R; 8 | 9 | /** 10 | * Created by ivy on 2017/9/19. 11 | * Description: 12 | */ 13 | 14 | public class MainJellyActivity extends AppCompatActivity { 15 | @Override 16 | protected void onCreate(@Nullable Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_jelly_main); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/main/IvyOnTouchListener.java: -------------------------------------------------------------------------------- 1 | package com.ivy.main; 2 | 3 | import android.content.Context; 4 | import android.graphics.Rect; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.GestureDetector; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | 10 | /** 11 | * Created by ivy on 2017/9/27. 12 | * Description: 13 | */ 14 | 15 | public abstract class IvyOnTouchListener extends RecyclerView.SimpleOnItemTouchListener { 16 | private RecyclerView mRecyclerView; 17 | private Context mContext; 18 | 19 | public IvyOnTouchListener(Context context){ 20 | this.mContext=context; 21 | } 22 | private GestureDetector mGestureDetector=new GestureDetector(mContext,new GestureDetector.SimpleOnGestureListener(){ 23 | @Override 24 | public boolean onSingleTapUp(MotionEvent e) { 25 | View childView = findChildViewUnder(e.getX(), e.getY()); 26 | if (childView != null) { 27 | int position = mRecyclerView.getChildLayoutPosition(childView); 28 | onItemClick(position, childView); 29 | return true; 30 | } 31 | 32 | if (onEmptyClick(e)){ 33 | return true; 34 | } 35 | return super.onSingleTapUp(e); 36 | } 37 | }); 38 | 39 | public View findChildViewUnder(float x, float y) { 40 | final int count = mRecyclerView.getChildCount(); 41 | Rect rect=new Rect(); 42 | for (int i = count - 1; i >= 0; i--) { 43 | final View child = mRecyclerView.getChildAt(i); 44 | child.getHitRect(rect); 45 | if (rect.contains((int) x,(int) y)){ 46 | return child; 47 | } 48 | } 49 | return null; 50 | } 51 | 52 | protected abstract boolean onEmptyClick(MotionEvent e); 53 | 54 | 55 | public abstract void onItemClick(int position, View childView); 56 | 57 | @Override 58 | public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { 59 | mRecyclerView=rv; 60 | return mGestureDetector.onTouchEvent(e); 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/main/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ivy.main; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.TextView; 14 | 15 | import com.ivy.bomb.BombActivity; 16 | import com.ivy.ruler.RulerActivity; 17 | import com.ivy.weather.R; 18 | import com.ivy.weather.WeatherActivity; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * Created by ivy on 2017/10/25. 25 | * Description: 26 | */ 27 | 28 | public class MainActivity extends AppCompatActivity { 29 | private RecyclerView rv; 30 | private List mData=new ArrayList<>(); 31 | @Override 32 | protected void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_main); 35 | addActivity(); 36 | rv= (RecyclerView) this.findViewById(R.id.rv); 37 | rv.setLayoutManager(new LinearLayoutManager(this)); 38 | rv.setAdapter(new MyAdapter()); 39 | rv.addOnItemTouchListener(new IvyOnTouchListener(this) { 40 | @Override 41 | protected boolean onEmptyClick(MotionEvent e) { 42 | return false; 43 | } 44 | 45 | @Override 46 | public void onItemClick(int position, View childView) { 47 | Intent intent=new Intent(MainActivity.this,mData.get(position).getClazz()); 48 | MainActivity.this.startActivity(intent); 49 | } 50 | }); 51 | } 52 | 53 | private void addActivity() { 54 | mData.clear(); 55 | mData.add(new ViewItem("天气小太阳", WeatherActivity.class)); 56 | mData.add(new ViewItem("薄荷卷尺", RulerActivity.class)); 57 | mData.add(new ViewItem("萌萌炸弹", BombActivity.class)); 58 | } 59 | 60 | private class MyAdapter extends RecyclerView.Adapter{ 61 | @Override 62 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 63 | return new ViewHolder(LayoutInflater.from(MainActivity.this).inflate(R.layout.adapter_main,parent,false)); 64 | } 65 | 66 | @Override 67 | public void onBindViewHolder(ViewHolder holder, int position) { 68 | holder.tv.setText(mData.get(position).getText()); 69 | } 70 | 71 | @Override 72 | public int getItemCount() { 73 | return mData.size(); 74 | } 75 | } 76 | 77 | private class ViewHolder extends RecyclerView.ViewHolder{ 78 | private TextView tv; 79 | public ViewHolder(View itemView) { 80 | super(itemView); 81 | tv= (TextView) itemView.findViewById(R.id.tv); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/main/ViewItem.java: -------------------------------------------------------------------------------- 1 | package com.ivy.main; 2 | 3 | import android.content.Intent; 4 | 5 | /** 6 | * Created by ivy on 2017/10/25. 7 | * Description: 8 | */ 9 | 10 | public class ViewItem { 11 | private String text; 12 | private Class clazz; 13 | 14 | public ViewItem(String text, Class clazz) { 15 | this.text = text; 16 | this.clazz = clazz; 17 | } 18 | 19 | public String getText() { 20 | 21 | return text; 22 | } 23 | 24 | public void setText(String text) { 25 | this.text = text; 26 | } 27 | 28 | public Class getClazz() { 29 | return clazz; 30 | } 31 | 32 | public void setClazz(Class clazz) { 33 | this.clazz = clazz; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/ruler/RulerActivity.java: -------------------------------------------------------------------------------- 1 | package com.ivy.ruler; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import com.ivy.weather.R; 8 | 9 | /** 10 | * Created by ivy on 2017/10/13. 11 | * Description: 12 | */ 13 | 14 | public class RulerActivity extends AppCompatActivity { 15 | @Override 16 | protected void onCreate(@Nullable Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_ruler); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/ruler/ScrollerRulerView.java: -------------------------------------------------------------------------------- 1 | package com.ivy.ruler; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.os.Build; 8 | import android.support.annotation.Nullable; 9 | import android.support.annotation.RequiresApi; 10 | import android.util.AttributeSet; 11 | import android.util.TypedValue; 12 | import android.view.GestureDetector; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | import android.view.ViewConfiguration; 16 | import android.view.animation.LinearInterpolator; 17 | import android.widget.Scroller; 18 | import java.text.DecimalFormat; 19 | 20 | 21 | /** 22 | * Created by ivy on 2017/10/13. 23 | * Description: 24 | */ 25 | 26 | public class ScrollerRulerView extends View{ 27 | private Paint mPaint; 28 | //刻度宽度,用于控制显示刻度的多少 29 | private int scaleWidth,scaleNum; 30 | //刻度高度 31 | private int minScaleHeight, maxScaleHeight ,selectScaleHeight; 32 | //颜色 33 | private int scaleColor,selectScaleColor,rulerColor; 34 | //刻度线的粗细 35 | private int scaleVerLineStroke, scaleHorLineStroke, ScaleHorLineEntireStroke, scaleHorLineSelectStroke; 36 | private int beginX=0;//滑动距离 37 | //刻度文字颜色 38 | private int scaleTextColor; 39 | //各个文字大小 40 | private float scaleTextSize,currentValueTextSize,currentValueUnitTextSize; 41 | //单位 42 | private String unit="KG"; 43 | private GestureDetector mGestureDetector=new GestureDetector(getContext(),new GestureDetector.SimpleOnGestureListener(){ 44 | @Override 45 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 46 | beginX-=distanceX; 47 | invalidate(); 48 | return true; 49 | } 50 | 51 | @Override 52 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 53 | if (Math.abs(velocityX)> ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity()){ 54 | mScroller.fling(beginX,0, (int) velocityX,0,beginX-getMeasuredWidth()*10,beginX+getMeasuredWidth()*10,0,0); 55 | invalidate(); 56 | return true; 57 | }else{ 58 | return false; 59 | } 60 | } 61 | 62 | @Override 63 | public boolean onSingleTapUp(MotionEvent e) { 64 | return super.onSingleTapUp(e); 65 | } 66 | 67 | }); 68 | 69 | public ScrollerRulerView(Context context) { 70 | super(context); 71 | init(); 72 | } 73 | 74 | public ScrollerRulerView(Context context, @Nullable AttributeSet attrs) { 75 | super(context, attrs); 76 | init(); 77 | } 78 | 79 | public ScrollerRulerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 80 | super(context, attrs, defStyleAttr); 81 | init(); 82 | } 83 | 84 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 85 | public ScrollerRulerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 86 | super(context, attrs, defStyleAttr, defStyleRes); 87 | init(); 88 | } 89 | 90 | @Override 91 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 92 | super.onMeasure(widthMeasureSpec,heightMeasureSpec); 93 | int width=getMeasuredWidth(); 94 | int height=getMeasuredHeight(); 95 | int minWidth= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,120,getContext().getResources().getDisplayMetrics()); 96 | int minHeight= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,80,getContext().getResources().getDisplayMetrics()); 97 | setMeasuredDimension(Math.max(minWidth,width),Math.max(minHeight,height)); 98 | } 99 | 100 | private void init() { 101 | mScroller=new Scroller(getContext(),new LinearInterpolator()); 102 | mPaint=new Paint(); 103 | mPaint.setAntiAlias(true); 104 | scaleTextSize=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,16,getContext().getResources().getDisplayMetrics()); 105 | currentValueTextSize=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,30,getContext().getResources().getDisplayMetrics()); 106 | currentValueUnitTextSize=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,12,getContext().getResources().getDisplayMetrics()); 107 | scaleVerLineStroke =2; 108 | scaleHorLineStroke =4; 109 | ScaleHorLineEntireStroke =6; 110 | scaleHorLineSelectStroke=8; 111 | rulerColor= Color.parseColor("#eeeeee"); 112 | scaleColor= Color.parseColor("#aaaaaa"); 113 | selectScaleColor=Color.parseColor("#00bbff"); 114 | scaleTextColor=Color.parseColor("#000000"); 115 | } 116 | 117 | @Override 118 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 119 | super.onSizeChanged(w, h, oldw, oldh); 120 | scaleWidth =w/40; 121 | selectScaleHeight = getMeasuredHeight()/4; 122 | maxScaleHeight =selectScaleHeight/8*7; 123 | minScaleHeight = (int) (selectScaleHeight/8*3.5f); 124 | scaleNum = w/scaleWidth/2+1; 125 | } 126 | 127 | @Override 128 | protected void onDraw(Canvas canvas) { 129 | super.onDraw(canvas); 130 | drawText(canvas); 131 | drawRuler(canvas); 132 | } 133 | 134 | private void drawRuler(Canvas canvas) { 135 | mPaint.setTextSize(scaleTextSize); 136 | //背景 137 | mPaint.setColor(rulerColor); 138 | canvas.drawRect(0,getMeasuredHeight()/2,getMeasuredWidth(),getMeasuredHeight(),mPaint); 139 | //横线 140 | mPaint.setColor(scaleColor); 141 | mPaint.setStrokeWidth(scaleVerLineStroke); 142 | canvas.drawLine(0,getMeasuredHeight()/2,getMeasuredWidth(),getMeasuredHeight()/2,mPaint); 143 | //刻度和刻度文字 144 | int afterX=getMeasuredWidth()/2+scaleWidth+beginX%scaleWidth; 145 | int afterValue=-beginX/scaleWidth+1; 146 | int previousX=getMeasuredWidth()/2+beginX%scaleWidth; 147 | int previousValue=-beginX/scaleWidth; 148 | for(int i=0;i scaleWidth / 2 ? (scaleWidth - Math.abs(beginX) % scaleWidth) * (beginX / Math.abs(beginX)) 222 | : Math.abs(beginX) % scaleWidth * (-beginX / Math.abs(beginX)); 223 | mScroller.startScroll(beginX, 0, dx, 0, 200); 224 | invalidate(); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/setting/SettingActivity.java: -------------------------------------------------------------------------------- 1 | package com.ivy.setting; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.ShortcutInfo; 6 | import android.content.pm.ShortcutManager; 7 | import android.graphics.drawable.Icon; 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.support.annotation.Nullable; 11 | import android.support.annotation.RequiresApi; 12 | import android.support.v7.app.AppCompatActivity; 13 | 14 | import com.ivy.weather.R; 15 | 16 | import java.util.ArrayList; 17 | import java.util.HashSet; 18 | import java.util.List; 19 | import java.util.Set; 20 | 21 | import static android.content.pm.ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION; 22 | 23 | /** 24 | * Created by ivy on 2017/9/22. 25 | * Description: 26 | */ 27 | 28 | public class SettingActivity extends AppCompatActivity { 29 | @RequiresApi(api = Build.VERSION_CODES.N_MR1) 30 | @Override 31 | protected void onCreate(@Nullable Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_setting); 34 | ShortcutManager shortCutManager = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE); 35 | //shortCutManager.removeAllDynamicShortcuts(); 36 | Intent intent=new Intent(); 37 | intent.setAction(Intent.ACTION_VIEW); 38 | intent.setClass(this,SettingActivity.class); 39 | Set category=new HashSet<>(); 40 | category.add(SHORTCUT_CATEGORY_CONVERSATION); 41 | List shortCutList=new ArrayList<>(); 42 | for(int i=0;i { 15 | private final CircleInfo mCircleInfo; 16 | 17 | public CircleTypeEvaluator(CircleInfo circleInfo) { 18 | this.mCircleInfo=circleInfo; 19 | } 20 | 21 | @Override 22 | public CircleInfo evaluate(float fraction, CircleInfo startValue, CircleInfo endValue) { 23 | float y = startValue.getY() + fraction * (endValue.getY() - startValue.getY()); 24 | float radius = startValue.getRadius() + fraction * (endValue.getRadius()-startValue.getRadius()); 25 | mCircleInfo.setCircleInfo(mCircleInfo.getX(),y,radius); 26 | return mCircleInfo; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/weather/WeatherActivity.java: -------------------------------------------------------------------------------- 1 | package com.ivy.weather; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | 8 | public class WeatherActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_weather); 14 | final WeatherView weatherView = (WeatherView) this.findViewById(R.id.weather_view); 15 | Button button= (Button) this.findViewById(R.id.btn_start); 16 | weatherView.setOnClickListener(new View.OnClickListener() { 17 | @Override 18 | public void onClick(View v) { 19 | weatherView.startAnim(); 20 | } 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/ivy/weather/WeatherView.java: -------------------------------------------------------------------------------- 1 | package com.ivy.weather; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.LinearGradient; 10 | import android.graphics.Paint; 11 | import android.graphics.Path; 12 | import android.graphics.RectF; 13 | import android.graphics.Shader; 14 | import android.os.Build; 15 | import android.os.Handler; 16 | import android.support.annotation.RequiresApi; 17 | import android.util.AttributeSet; 18 | import android.view.View; 19 | import android.view.animation.AccelerateDecelerateInterpolator; 20 | import android.view.animation.DecelerateInterpolator; 21 | import android.view.animation.LinearInterpolator; 22 | 23 | import java.util.HashMap; 24 | import java.util.Iterator; 25 | import java.util.Map; 26 | import java.util.concurrent.ConcurrentHashMap; 27 | 28 | /** 29 | * Created by ivy on 2017/8/11. 30 | * Description: 31 | */ 32 | 33 | public class WeatherView extends View { 34 | private Paint mPaint; 35 | private RectF mRectCenterArc; 36 | private RectF mRectOutSideArc; 37 | private RectF mRectFSunFlower; 38 | private RectF mRectFCloudShadow; 39 | private RectF mRectFSunShadow; 40 | private float minRingCenterWidth; 41 | private float ringWidth; 42 | private int ringColor=Color.parseColor("#ffcf45"); 43 | private float centerArcAngle, centerArcEndAngle; 44 | private float outSideArcAngle,outSideArcStartAngle; 45 | private boolean isDrawArcLine=false; 46 | private boolean isDrawSun=false; 47 | private boolean isDrawRing=false; 48 | private boolean isDrawCloud=false; 49 | private boolean isDrawCloudShadow=false; 50 | private boolean isDrawSunShadow=false; 51 | private float sunWidth; 52 | private float finalSunWidth; 53 | private float maxSunFlowerWidth; 54 | private float sunRotateAngle=0; 55 | private int cloudShadowAlpha =0; 56 | private Path mPath; 57 | private Path mCloudShadowPath; 58 | //云朵的组成部分信息 59 | private CircleInfo mCircleInfoTopOne,mCircleInfoTopTwo,mCircleInfoBottomOne,mCircleInfoBottomTwo,mCircleInfoBottomThree; 60 | //太阳花朵,白云的Shade 61 | private LinearGradient mCloudLinearGradient,mFlowerLinearGradient,mFlowerRotateLinearGradient; 62 | private int cloudShadowColor=Color.parseColor("#bc9a31"); 63 | private float sunShadowWidth, sunShadowHeight; 64 | private int sunShadowColor=Color.parseColor("#bac3c3"); 65 | //存储所有动画的ValueAnimator方便管理 66 | private ConcurrentHashMap animMap=new ConcurrentHashMap<>(); 67 | //动画是否开始 68 | private boolean isStart=false; 69 | 70 | public WeatherView(Context context) { 71 | super(context); 72 | init(); 73 | } 74 | 75 | public WeatherView(Context context, AttributeSet attrs) { 76 | super(context, attrs); 77 | init(); 78 | } 79 | 80 | public WeatherView(Context context, AttributeSet attrs, int defStyleAttr) { 81 | super(context, attrs, defStyleAttr); 82 | init(); 83 | } 84 | 85 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 86 | public WeatherView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 87 | super(context, attrs, defStyleAttr, defStyleRes); 88 | init(); 89 | } 90 | 91 | @Override 92 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 93 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 94 | int width=getMeasuredWidth(); 95 | setMeasuredDimension(width, (int) (width*1.4f)); 96 | } 97 | 98 | @Override 99 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 100 | super.onSizeChanged(w, h, oldw, oldh); 101 | } 102 | 103 | public void init(){ 104 | mPaint=new Paint(); 105 | mPaint.setDither(true); 106 | mPaint.setAntiAlias(true); 107 | mRectCenterArc=new RectF(); 108 | mRectOutSideArc =new RectF(); 109 | mRectFSunFlower =new RectF(); 110 | mRectFCloudShadow =new RectF(); 111 | mRectFSunShadow =new RectF(); 112 | mPath=new Path(); 113 | mCloudShadowPath =new Path(); 114 | mCircleInfoTopOne=new CircleInfo(); 115 | mCircleInfoTopTwo=new CircleInfo(); 116 | mCircleInfoBottomOne=new CircleInfo(); 117 | mCircleInfoBottomTwo=new CircleInfo(); 118 | mCircleInfoBottomThree=new CircleInfo(); 119 | } 120 | 121 | @Override 122 | protected void onDraw(Canvas canvas) { 123 | super.onDraw(canvas); 124 | if (isDrawRing) 125 | drawZoomRing(canvas); 126 | if (isDrawArcLine) 127 | drawArcLine(canvas); 128 | if (isDrawSun) 129 | drawSun(canvas); 130 | if (isDrawSunShadow) 131 | drawSunShadow(canvas); 132 | if (isDrawCloudShadow) 133 | drawCloudShadow(canvas); 134 | if (isDrawCloud) 135 | drawCloud(canvas); 136 | } 137 | 138 | private void drawSunShadow(Canvas canvas) { 139 | mPaint.setColor(sunShadowColor); 140 | mPaint.setStyle(Paint.Style.FILL); 141 | mRectFSunShadow.set(getMeasuredWidth()/2-sunShadowWidth/2,getMeasuredHeight()- sunShadowHeight, 142 | getMeasuredWidth()/2+sunShadowWidth/2,getMeasuredHeight()); 143 | canvas.drawOval(mRectFSunShadow,mPaint); 144 | } 145 | 146 | private void drawCloudShadow(Canvas canvas) { 147 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 148 | mPaint.setColor(cloudShadowColor); 149 | mPaint.setAlpha(cloudShadowAlpha); 150 | canvas.save(); 151 | canvas.clipRect(0,getMeasuredHeight()/2+getMeasuredWidth()/7f,getMeasuredWidth(),getMeasuredHeight()); 152 | mRectFCloudShadow.set(getMeasuredWidth()/2-finalSunWidth/2,getMeasuredHeight()/2-finalSunWidth/2,getMeasuredWidth()/2+finalSunWidth/2,getMeasuredHeight()/2+finalSunWidth/2); 153 | mCloudShadowPath.reset(); 154 | mCloudShadowPath.moveTo(mCircleInfoBottomOne.getX(),getMeasuredHeight()/2+getMeasuredWidth()/7f); 155 | mCloudShadowPath.arcTo(mRectFCloudShadow,15,45,false); 156 | canvas.drawPath(mCloudShadowPath,mPaint); 157 | canvas.restore(); 158 | mPaint.setAlpha(255); 159 | } 160 | 161 | private void drawCloud(Canvas canvas) { 162 | mPath.reset(); 163 | mPaint.setShader(mCloudLinearGradient); 164 | if (mCircleInfoBottomOne.isCanDraw()) 165 | mPath.addCircle(mCircleInfoBottomOne.getX(),mCircleInfoBottomOne.getY(),mCircleInfoBottomOne.getRadius(), Path.Direction.CW);//左下1 166 | if (mCircleInfoBottomTwo.isCanDraw()) 167 | mPath.addCircle(mCircleInfoBottomTwo.getX(),mCircleInfoBottomTwo.getY(),mCircleInfoBottomTwo.getRadius(), Path.Direction.CW);//底部2 168 | if (mCircleInfoBottomThree.isCanDraw()) 169 | mPath.addCircle(mCircleInfoBottomThree.getX(),mCircleInfoBottomThree.getY(),mCircleInfoBottomThree.getRadius(), Path.Direction.CW);//底3 170 | if (mCircleInfoTopOne.isCanDraw()) 171 | mPath.addCircle(mCircleInfoTopOne.getX(),mCircleInfoTopOne.getY(),mCircleInfoTopOne.getRadius(), Path.Direction.CW);//顶1 172 | if (mCircleInfoTopTwo.isCanDraw()) 173 | mPath.addCircle(mCircleInfoTopTwo.getX(),mCircleInfoTopTwo.getY(),mCircleInfoTopTwo.getRadius(), Path.Direction.CW);//顶2 174 | canvas.save(); 175 | canvas.clipRect(0,0,getMeasuredWidth(),getMeasuredHeight()/2+getMeasuredWidth()/7f); 176 | canvas.drawPath(mPath,mPaint); 177 | canvas.restore(); 178 | mPaint.setShader(null); 179 | } 180 | 181 | private void drawSun(Canvas canvas) { 182 | mPaint.setStrokeWidth(0); 183 | mPaint.setStyle(Paint.Style.FILL); 184 | mPaint.setColor(ringColor); 185 | mPaint.setShader(mFlowerLinearGradient); 186 | canvas.save(); 187 | canvas.rotate(sunRotateAngle,getMeasuredWidth()/2,getMeasuredHeight()/2); 188 | canvas.drawRect(mRectFSunFlower,mPaint); 189 | canvas.rotate(45,getMeasuredWidth()/2,getMeasuredHeight()/2); 190 | mPaint.setShader(mFlowerRotateLinearGradient); 191 | canvas.drawRect(mRectFSunFlower,mPaint); 192 | canvas.restore(); 193 | mPaint.setShader(null); 194 | canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,sunWidth/2,mPaint); 195 | } 196 | 197 | private void drawArcLine(Canvas canvas) { 198 | mPaint.setColor(ringColor); 199 | mPaint.setStyle(Paint.Style.STROKE); 200 | mPaint.setStrokeCap(Paint.Cap.ROUND); 201 | mPaint.setStrokeWidth(getMeasuredWidth()/40); 202 | canvas.drawArc(mRectCenterArc, centerArcEndAngle-centerArcAngle,centerArcAngle,false,mPaint); 203 | mPaint.setStrokeWidth(getMeasuredWidth()/25); 204 | canvas.drawArc(mRectOutSideArc,outSideArcStartAngle,outSideArcAngle,false,mPaint); 205 | } 206 | 207 | private void drawZoomRing(Canvas canvas) { 208 | mPaint.setShader(null); 209 | mPaint.setStrokeWidth(0); 210 | mPaint.setStyle(Paint.Style.FILL); 211 | mPaint.setColor(ringColor); 212 | canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,ringWidth/2,mPaint); 213 | mPaint.setColor(Color.WHITE); 214 | canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,minRingCenterWidth/2,mPaint); 215 | } 216 | 217 | public void startAnim() { 218 | resetAnim(); 219 | startInvalidateAnim(); 220 | } 221 | 222 | private static final String ANIM_CONTROL_INVALIDATE="anim_control_invalidate"; 223 | private void startInvalidateAnim() { 224 | isStart=true; 225 | ValueAnimator valueAnimator=animMap.get(ANIM_CONTROL_INVALIDATE); 226 | if (valueAnimator==null){ 227 | valueAnimator=ValueAnimator.ofFloat(0,1); 228 | valueAnimator.setInterpolator(new LinearInterpolator()); 229 | valueAnimator.setDuration(300); 230 | valueAnimator.setRepeatCount(ValueAnimator.INFINITE); 231 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 232 | @Override 233 | public void onAnimationUpdate(ValueAnimator animation) { 234 | invalidate(); 235 | } 236 | }); 237 | valueAnimator.addListener(new AnimatorListenerAdapter() { 238 | @Override 239 | public void onAnimationStart(Animator animation) { 240 | super.onAnimationStart(animation); 241 | startRing(); 242 | } 243 | }); 244 | animMap.put(ANIM_CONTROL_INVALIDATE,valueAnimator); 245 | } 246 | startValueAnimator(valueAnimator); 247 | } 248 | 249 | 250 | public static final String ANIM_CLOUD_TOP_ONE="anim_cloud_top_one"; 251 | public static final String ANIM_CLOUD_TOP_TWO="anim_cloud_top_two"; 252 | public static final String ANIM_CLOUD_BOTTOM_ONE="anim_cloud_bottom_one"; 253 | public static final String ANIM_CLOUD_BOTTOM_TWO="anim_cloud_bottom_two"; 254 | public static final String ANIM_CLOUD_BOTTOM_THREE="anim_cloud_bottom_three"; 255 | public void startCloud(){ 256 | isDrawCloud=true; 257 | startCloudTemplate(ANIM_CLOUD_BOTTOM_ONE,0,mCircleInfoBottomOne); 258 | startCloudTemplate(ANIM_CLOUD_BOTTOM_TWO,200,mCircleInfoBottomTwo); 259 | startCloudTemplate(ANIM_CLOUD_BOTTOM_THREE,450,mCircleInfoBottomThree); 260 | startCloudTemplate(ANIM_CLOUD_TOP_ONE,300,mCircleInfoTopOne); 261 | startCloudTemplate(ANIM_CLOUD_TOP_TWO,350,mCircleInfoTopTwo); 262 | this.postDelayed(new Runnable() { 263 | @Override 264 | public void run() { 265 | startCloudShadow(); 266 | } 267 | },600); 268 | } 269 | 270 | private void startCloudTemplate(String mapTag, long delay, final CircleInfo circleInfo){ 271 | ValueAnimator valueAnimator=animMap.get(mapTag); 272 | if (valueAnimator==null){ 273 | valueAnimator=ValueAnimator.ofObject(new CircleTypeEvaluator(circleInfo),new CircleInfo(circleInfo.getX(),circleInfo.getY()+circleInfo.getRadius(),0) 274 | ,new CircleInfo(circleInfo.getX(),circleInfo.getY(),circleInfo.getRadius())); 275 | valueAnimator.setDuration(600); 276 | valueAnimator.addListener(new AnimatorListenerAdapter() { 277 | @Override 278 | public void onAnimationStart(Animator animation) { 279 | super.onAnimationStart(animation); 280 | circleInfo.setCanDraw(true); 281 | } 282 | }); 283 | animMap.put(mapTag,valueAnimator); 284 | } 285 | valueAnimator.setObjectValues(new CircleInfo(circleInfo.getX(),circleInfo.getY()+circleInfo.getRadius(),0) 286 | ,new CircleInfo(circleInfo.getX(),circleInfo.getY(),circleInfo.getRadius())); 287 | valueAnimator.setStartDelay(delay); 288 | startValueAnimator(valueAnimator); 289 | } 290 | 291 | public static final String ANIM_CLOUD_SHADOW="anim_cloud_shadow"; 292 | private void startCloudShadow() { 293 | isDrawCloudShadow=true; 294 | ValueAnimator valueAnimator=animMap.get(ANIM_CLOUD_SHADOW); 295 | if (valueAnimator==null){ 296 | valueAnimator=ValueAnimator.ofInt(0,255).setDuration(600); 297 | valueAnimator.setInterpolator(new LinearInterpolator()); 298 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 299 | @Override 300 | public void onAnimationUpdate(ValueAnimator animation) { 301 | cloudShadowAlpha= (int) animation.getAnimatedValue(); 302 | } 303 | }); 304 | animMap.put(ANIM_CLOUD_SHADOW,valueAnimator); 305 | } 306 | startValueAnimator(valueAnimator); 307 | } 308 | 309 | public static final String ANIM_SUN_ZOOM="anim_sun_zoom"; 310 | public static final String ANIM_FLOWER_ZOOM="anim_flower_zoom"; 311 | private void startSun() { 312 | isDrawSun=true; 313 | //太阳内部圆 314 | ValueAnimator sunAnim= animMap.get(ANIM_SUN_ZOOM); 315 | if (sunAnim==null){ 316 | sunAnim=ValueAnimator.ofFloat().setDuration(400); 317 | sunAnim.setInterpolator(new AccelerateDecelerateInterpolator()); 318 | sunAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 319 | @Override 320 | public void onAnimationUpdate(ValueAnimator animation) { 321 | sunWidth= (float) animation.getAnimatedValue(); 322 | } 323 | }); 324 | animMap.put(ANIM_SUN_ZOOM,sunAnim); 325 | } 326 | sunAnim.setFloatValues(sunWidth,getMeasuredWidth(),finalSunWidth); 327 | startValueAnimator(sunAnim); 328 | //太阳光环 329 | ValueAnimator flowerAnim= animMap.get(ANIM_FLOWER_ZOOM); 330 | if (flowerAnim==null){ 331 | flowerAnim=ValueAnimator.ofFloat().setDuration(400); 332 | flowerAnim.setInterpolator(new AccelerateDecelerateInterpolator()); 333 | flowerAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 334 | @Override 335 | public void onAnimationUpdate(ValueAnimator animation) { 336 | float width= (float) animation.getAnimatedValue(); 337 | mRectFSunFlower.set(getMeasuredWidth()/2f-width/2,getMeasuredHeight()/2f-width/2 338 | ,getMeasuredWidth()/2f+width/2,getMeasuredHeight()/2f+width/2); 339 | } 340 | }); 341 | flowerAnim.addListener(new AnimatorListenerAdapter() { 342 | @Override 343 | public void onAnimationEnd(Animator animation) { 344 | super.onAnimationEnd(animation); 345 | startSunRotate(); 346 | } 347 | }); 348 | flowerAnim.setStartDelay(100); 349 | animMap.put(ANIM_FLOWER_ZOOM,flowerAnim); 350 | } 351 | flowerAnim.setFloatValues(0,maxSunFlowerWidth,maxSunFlowerWidth*0.9f); 352 | startValueAnimator(flowerAnim); 353 | this.postDelayed(new Runnable() { 354 | @Override 355 | public void run() { 356 | startCloud(); 357 | } 358 | },300); 359 | 360 | this.postDelayed(new Runnable() { 361 | @Override 362 | public void run() { 363 | startSunShadow(); 364 | } 365 | },200); 366 | } 367 | 368 | public static final String ANIM_WEATHER_SHADOW ="anim_weather_shadow"; 369 | private void startSunShadow() { 370 | isDrawSunShadow=true; 371 | ValueAnimator valueAnimator=animMap.get(ANIM_WEATHER_SHADOW); 372 | if (valueAnimator==null){ 373 | valueAnimator=ValueAnimator.ofFloat().setDuration(400); 374 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 375 | @Override 376 | public void onAnimationUpdate(ValueAnimator animation) { 377 | sunShadowWidth= (float) animation.getAnimatedValue(); 378 | } 379 | }); 380 | animMap.put(ANIM_WEATHER_SHADOW,valueAnimator); 381 | } 382 | valueAnimator.setFloatValues(0,getMeasuredWidth(),getMeasuredWidth()*0.8f); 383 | startValueAnimator(valueAnimator); 384 | } 385 | 386 | public static final String ANIM_SUN_ROTATE="anim_sun_rotate"; 387 | private void startSunRotate() { 388 | ValueAnimator rotateAnim=animMap.get(ANIM_SUN_ROTATE); 389 | if (rotateAnim==null){ 390 | rotateAnim =ValueAnimator.ofFloat(0,360f).setDuration(30*1000); 391 | rotateAnim.setRepeatCount(ValueAnimator.INFINITE); 392 | rotateAnim.setInterpolator(new LinearInterpolator()); 393 | rotateAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 394 | @Override 395 | public void onAnimationUpdate(ValueAnimator animation) { 396 | sunRotateAngle= (float) animation.getAnimatedValue(); 397 | } 398 | }); 399 | animMap.put(ANIM_SUN_ROTATE,rotateAnim); 400 | } 401 | startValueAnimator(rotateAnim); 402 | } 403 | 404 | public static final String ANIM_ARC_LINE_CENTER_ANGLE="anim_arc_line_center_angle"; 405 | public static final String ANIM_ARC_LINE_CENTER_MOVE ="anim_arc_line_center_move"; 406 | public static final String ANIM_ARC_LINE_OUTSIZE_ANGLE="anim_arc_line_outsize_angle"; 407 | public static final String ANIM_ARC_LINE_OUTSIZE_MOVE ="anim_arc_line_outsize_move"; 408 | private void startArcLine() { 409 | isDrawArcLine=true; 410 | //内部圆弧长度控制 411 | ValueAnimator centerArcLineAngleAnim= animMap.get(ANIM_ARC_LINE_CENTER_ANGLE); 412 | if (centerArcLineAngleAnim==null){ 413 | centerArcLineAngleAnim=ValueAnimator.ofFloat().setDuration(500); 414 | centerArcLineAngleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 415 | @Override 416 | public void onAnimationUpdate(ValueAnimator animation) { 417 | centerArcAngle= (float) animation.getAnimatedValue(); 418 | } 419 | }); 420 | animMap.put(ANIM_ARC_LINE_CENTER_ANGLE,centerArcLineAngleAnim); 421 | } 422 | centerArcLineAngleAnim.setFloatValues(centerArcAngle,180,0); 423 | startValueAnimator(centerArcLineAngleAnim); 424 | //内部圆弧移动控制 425 | ValueAnimator centerArcLineMoveAnim= animMap.get(ANIM_ARC_LINE_CENTER_MOVE); 426 | if (centerArcLineMoveAnim==null){ 427 | centerArcLineMoveAnim=ValueAnimator.ofFloat().setDuration(400); 428 | centerArcLineMoveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 429 | @Override 430 | public void onAnimationUpdate(ValueAnimator animation) { 431 | centerArcEndAngle = (float) animation.getAnimatedValue(); 432 | } 433 | }); 434 | centerArcLineMoveAnim.addListener(new AnimatorListenerAdapter() { 435 | @Override 436 | public void onAnimationEnd(Animator animation) { 437 | super.onAnimationEnd(animation); 438 | isDrawArcLine=false; 439 | } 440 | }); 441 | animMap.put(ANIM_ARC_LINE_CENTER_MOVE,centerArcLineMoveAnim); 442 | } 443 | centerArcLineMoveAnim.setFloatValues(centerArcEndAngle,630); 444 | startValueAnimator(centerArcLineMoveAnim); 445 | //外部圆弧长度控制 446 | ValueAnimator outSizeArcLineAngleAnim= animMap.get(ANIM_ARC_LINE_OUTSIZE_ANGLE); 447 | if (outSizeArcLineAngleAnim==null){ 448 | outSizeArcLineAngleAnim=ValueAnimator.ofFloat().setDuration(400); 449 | outSizeArcLineAngleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 450 | @Override 451 | public void onAnimationUpdate(ValueAnimator animation) { 452 | outSideArcAngle= (float) animation.getAnimatedValue(); 453 | } 454 | }); 455 | animMap.put(ANIM_ARC_LINE_OUTSIZE_ANGLE,outSizeArcLineAngleAnim); 456 | } 457 | outSizeArcLineAngleAnim.setFloatValues(outSideArcAngle,180,0); 458 | startValueAnimator(outSizeArcLineAngleAnim); 459 | //外部圆弧移动控制 460 | ValueAnimator outSizeArcLineMoveAnim= animMap.get(ANIM_ARC_LINE_OUTSIZE_MOVE); 461 | if (outSizeArcLineMoveAnim==null){ 462 | outSizeArcLineMoveAnim=ValueAnimator.ofFloat().setDuration(300); 463 | outSizeArcLineMoveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 464 | @Override 465 | public void onAnimationUpdate(ValueAnimator animation) { 466 | outSideArcStartAngle= (float) animation.getAnimatedValue(); 467 | } 468 | }); 469 | animMap.put(ANIM_ARC_LINE_OUTSIZE_MOVE,outSizeArcLineMoveAnim); 470 | } 471 | outSizeArcLineMoveAnim.setFloatValues(outSideArcStartAngle,-90); 472 | startValueAnimator(outSizeArcLineMoveAnim); 473 | this.postDelayed(new Runnable() { 474 | @Override 475 | public void run() { 476 | startSun(); 477 | } 478 | },250); 479 | } 480 | 481 | public static final String ANIM_RING_ZOOM="anim_ring_zoom"; 482 | public static final String ANIM_RING_CIRCLE_ZOOM="anim_ring_circle_zoom"; 483 | private void startRing() { 484 | isDrawRing=true; 485 | ValueAnimator zoomAnim= animMap.get(ANIM_RING_ZOOM); 486 | if (zoomAnim==null){ 487 | zoomAnim= ValueAnimator.ofFloat().setDuration(500); 488 | zoomAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 489 | @Override 490 | public void onAnimationUpdate(ValueAnimator animation) { 491 | ringWidth= (float) animation.getAnimatedValue(); 492 | } 493 | }); 494 | zoomAnim.addListener(new AnimatorListenerAdapter() { 495 | @Override 496 | public void onAnimationEnd(Animator animation) { 497 | super.onAnimationEnd(animation); 498 | startArcLine(); 499 | } 500 | }); 501 | animMap.put(ANIM_RING_ZOOM,zoomAnim); 502 | } 503 | zoomAnim.setFloatValues(ringWidth,getMeasuredWidth()*0.8f); 504 | startValueAnimator(zoomAnim); 505 | 506 | ValueAnimator circleZoom= animMap.get(ANIM_RING_CIRCLE_ZOOM); 507 | if (circleZoom==null){ 508 | circleZoom=ValueAnimator.ofFloat().setDuration(300); 509 | circleZoom.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 510 | @Override 511 | public void onAnimationUpdate(ValueAnimator animation) { 512 | minRingCenterWidth= (float) animation.getAnimatedValue(); 513 | } 514 | }); 515 | circleZoom.setStartDelay(300); 516 | circleZoom.addListener(new AnimatorListenerAdapter() { 517 | @Override 518 | public void onAnimationEnd(Animator animation) { 519 | super.onAnimationEnd(animation); 520 | isDrawRing=false; 521 | } 522 | }); 523 | animMap.put(ANIM_RING_CIRCLE_ZOOM,circleZoom); 524 | } 525 | circleZoom.setFloatValues(minRingCenterWidth,getMeasuredWidth()*0.8f); 526 | startValueAnimator(circleZoom); 527 | } 528 | 529 | private void startValueAnimator(ValueAnimator valueAnimator){ 530 | if (isStart) { 531 | valueAnimator.start(); 532 | } 533 | } 534 | 535 | private void resetAnim() { 536 | stopAnimAndRemoveCallbacks(); 537 | //是否画相应的内容 538 | isDrawArcLine=false; 539 | isDrawSun=false; 540 | isDrawRing=false; 541 | isDrawCloud=false; 542 | isDrawCloudShadow=false; 543 | isDrawSunShadow=false; 544 | //圆环部分 545 | minRingCenterWidth=10; 546 | ringWidth=3*minRingCenterWidth; 547 | //圆弧部分 548 | centerArcAngle=2; 549 | centerArcEndAngle =270+centerArcAngle; 550 | outSideArcStartAngle=180; 551 | outSideArcAngle=90; 552 | mRectCenterArc.set(getMeasuredWidth()/2-getMeasuredWidth()/6,getMeasuredHeight()/2-getMeasuredWidth()/6 553 | ,getMeasuredWidth()/2+getMeasuredWidth()/6,getMeasuredHeight()/2+getMeasuredWidth()/6); 554 | mRectOutSideArc.set(getMeasuredWidth()/2-getMeasuredWidth()/4,getMeasuredHeight()/2-getMeasuredWidth()/4 555 | ,getMeasuredWidth()/2+getMeasuredWidth()/4,getMeasuredHeight()/2+getMeasuredWidth()/4); 556 | 557 | //整个天气View的阴影 558 | sunShadowHeight =getMeasuredWidth()/25; 559 | //太阳旋转 560 | sunRotateAngle=0; 561 | //太阳出现 562 | sunWidth=0; 563 | finalSunWidth =getMeasuredWidth()*0.7f; 564 | maxSunFlowerWidth= (float) Math.sqrt(Math.pow(getMeasuredWidth()/2.0f,2)*2); 565 | mRectFSunFlower.set(0,0,0,0); 566 | mFlowerLinearGradient=new LinearGradient(getMeasuredWidth()/2-maxSunFlowerWidth/2,getMeasuredHeight()/2-maxSunFlowerWidth/2, 567 | getMeasuredWidth()/2+maxSunFlowerWidth/2,getMeasuredHeight()/2+maxSunFlowerWidth/2, 568 | new int[]{Color.parseColor("#fff38e"),Color.parseColor("#ebb228") ,Color.parseColor("#ae8200")},new float[]{0,0.5f,1}, Shader.TileMode.REPEAT); 569 | mFlowerRotateLinearGradient=new LinearGradient(getMeasuredWidth()/2-maxSunFlowerWidth/2,getMeasuredHeight()/2-maxSunFlowerWidth/2, 570 | getMeasuredWidth()/2+maxSunFlowerWidth/2,getMeasuredHeight()/2-maxSunFlowerWidth/2, 571 | Color.parseColor("#f7b600") ,Color.parseColor("#ae8200"), Shader.TileMode.REPEAT); 572 | //云 573 | mCircleInfoBottomOne.setCircleInfo(getMeasuredWidth()/2,getMeasuredHeight()/2+getMeasuredWidth()/10,getMeasuredWidth()/10,false);//左下1 574 | mCircleInfoTopOne.setCircleInfo(getMeasuredWidth()/2+getMeasuredWidth()/14,getMeasuredHeight()/2-getMeasuredWidth()/30,getMeasuredWidth()/9,false);//顶1 575 | mCircleInfoBottomTwo.setCircleInfo(getMeasuredWidth()/2+getMeasuredWidth()/10*1.8f,getMeasuredHeight()/2+getMeasuredWidth()/10,getMeasuredWidth()/9,false);//底部2 576 | mCircleInfoTopTwo.setCircleInfo(getMeasuredWidth()/2+getMeasuredWidth()/10*2.2f,getMeasuredHeight()/2-getMeasuredWidth()/120,getMeasuredWidth()/10,false);//顶2 577 | mCircleInfoBottomThree.setCircleInfo(getMeasuredWidth()/2+getMeasuredWidth()/10*3.5f,getMeasuredHeight()/2+getMeasuredWidth()/10,getMeasuredWidth()/12,false);//底3 578 | mCloudLinearGradient =new LinearGradient(mCircleInfoBottomOne.getX()-mCircleInfoBottomOne.getRadius(),mCircleInfoTopOne.getY()-mCircleInfoTopOne.getRadius(),getMeasuredWidth(),getMeasuredHeight()/2+getMeasuredWidth()/7f, 579 | new int[]{Color.parseColor("#fcfbf3"),Color.parseColor("#efebdf") ,Color.parseColor("#d7d7c7")},new float[]{0,0.5f,1}, Shader.TileMode.REPEAT); 580 | //云阴影 581 | cloudShadowAlpha=0; 582 | invalidate(); 583 | } 584 | 585 | @Override 586 | protected void onDetachedFromWindow() { 587 | super.onDetachedFromWindow(); 588 | stopAnimAndRemoveCallbacks(); 589 | } 590 | 591 | private void stopAnimAndRemoveCallbacks(){ 592 | isStart=false; 593 | for (Map.Entry entry : animMap.entrySet()) { 594 | entry.getValue().end(); 595 | } 596 | Handler handler=this.getHandler(); 597 | if (handler!=null){ 598 | handler.removeCallbacksAndMessages(null); 599 | } 600 | } 601 | } 602 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/a.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengivy/Weather/93bf44c991ea3068e4db3917a0de9920fd7b4868/app/src/main/res/drawable/a.gif -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_bomb.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_jelly_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_ruler.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_weather.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 20 |