├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── BeiSaierLearn.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── xulinggang │ │ └── beisaierlearn │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── xlg │ │ ├── beisaierlearn │ │ ├── Log.java │ │ ├── MainActivity.java │ │ └── SecondActivity.java │ │ ├── util │ │ └── Util.java │ │ └── weidget │ │ ├── BeiSaiErView.java │ │ ├── BitmapMeshView2.java │ │ ├── DrawDottedCurve.java │ │ ├── HeartFlowLayout.java │ │ └── PathMeasureView.java │ └── res │ ├── layout │ ├── activity_main.xml │ └── activity_second.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ ├── bt.jpg │ ├── heart1.png │ ├── heart2.png │ ├── heart3.png │ ├── heart4.png │ ├── ic_launcher.png │ └── image.jpg │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | BeiSaierLearn -------------------------------------------------------------------------------- /.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/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 1.8 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /BeiSaierLearn.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeiSaierLearn 2 | 贝塞尔效果,模仿苹果神奇效果 3 | 4 | 主要用到知识点:自定义属性动画(TypeEvaluator 实现贝塞尔效果)、PathMeasure(计算Path长度)、drawBitmapMesh用法、一元3次方程求解。 5 | 6 | 先看下效果: 7 | ![Alt Text](http://7xrmxo.com1.z0.glb.clouddn.com/anim.gif) 8 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.xlg.beisaierlearn" 9 | minSdkVersion 15 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | debug{ 16 | buildConfigField "boolean", "LOG_DEBUG", "true" 17 | } 18 | 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | compile fileTree(dir: 'libs', include: ['*.jar']) 28 | compile 'com.android.support:appcompat-v7:22.2.0' 29 | } 30 | -------------------------------------------------------------------------------- /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/xulinggang/Library/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/example/xulinggang/beisaierlearn/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.example.xulinggang.beisaierlearn; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/xlg/beisaierlearn/Log.java: -------------------------------------------------------------------------------- 1 | package com.xlg.beisaierlearn; 2 | 3 | /** 4 | * Created by xulinggang on 15/6/29. 5 | */ 6 | public class Log { 7 | 8 | public static void i(String tag, String msg) { 9 | if (BuildConfig.LOG_DEBUG) { 10 | android.util.Log.i(tag,msg); 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/xlg/beisaierlearn/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xlg.beisaierlearn; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | import android.view.Window; 11 | import android.view.WindowManager; 12 | import android.view.animation.AnticipateOvershootInterpolator; 13 | import android.view.animation.LinearInterpolator; 14 | import android.widget.Button; 15 | import android.widget.TextView; 16 | 17 | import com.xlg.beisaierlearn.R; 18 | import com.xlg.weidget.BeiSaiErView; 19 | import com.xlg.weidget.HeartFlowLayout; 20 | 21 | 22 | public class MainActivity extends Activity { 23 | private BeiSaiErView mBeiSaiErView; 24 | private Button mButton; 25 | private TextView mHello; 26 | private HeartFlowLayout mHeartFlowLayout; 27 | 28 | private Button mButtonNext; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | /*set it to be no title*/ 34 | requestWindowFeature(Window.FEATURE_NO_TITLE); 35 | /*set it to be full screen*/ 36 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 37 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 38 | 39 | 40 | setContentView(R.layout.activity_main); 41 | 42 | mBeiSaiErView = (BeiSaiErView) findViewById(R.id.beisai_view); 43 | mButton = (Button) findViewById(R.id.btn_click); 44 | mButtonNext = (Button) findViewById(R.id.btn_next); 45 | 46 | mHello = (TextView) findViewById(R.id.hello_world); 47 | 48 | LinearInterpolator interpolator = new LinearInterpolator(); 49 | final ObjectAnimator animator = ObjectAnimator.ofFloat(mHello,"translationY",0,1920); 50 | animator.setInterpolator(interpolator); 51 | animator.setDuration(3000); 52 | 53 | mHeartFlowLayout = (HeartFlowLayout) findViewById(R.id.heart_flow); 54 | mButton.setOnClickListener(new View.OnClickListener() { 55 | @Override 56 | public void onClick(View v) { 57 | mBeiSaiErView.startRun(); 58 | animator.start(); 59 | } 60 | 61 | }); 62 | 63 | mButtonNext.setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | Intent intent = new Intent(MainActivity.this, SecondActivity.class); 67 | startActivity(intent); 68 | } 69 | }); 70 | 71 | 72 | } 73 | 74 | @Override 75 | public boolean onCreateOptionsMenu(Menu menu) { 76 | // Inflate the menu; this adds items to the action bar if it is present. 77 | getMenuInflater().inflate(R.menu.menu_main, menu); 78 | return true; 79 | } 80 | 81 | @Override 82 | public boolean onOptionsItemSelected(MenuItem item) { 83 | // Handle action bar item clicks here. The action bar will 84 | // automatically handle clicks on the Home/Up button, so long 85 | // as you specify a parent activity in AndroidManifest.xml. 86 | int id = item.getItemId(); 87 | 88 | //noinspection SimplifiableIfStatement 89 | if (id == R.id.action_settings) { 90 | return true; 91 | } 92 | 93 | return super.onOptionsItemSelected(item); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/xlg/beisaierlearn/SecondActivity.java: -------------------------------------------------------------------------------- 1 | package com.xlg.beisaierlearn; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.widget.RelativeLayout; 6 | 7 | import com.xlg.weidget.DrawDottedCurve; 8 | 9 | /** 10 | * Created by xulinggang on 15/6/26. 11 | */ 12 | public class SecondActivity extends Activity { 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_second); 17 | 18 | DrawDottedCurve curve = new DrawDottedCurve(this,0,200,400,400); 19 | RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(-2,-2); 20 | curve.setLayoutParams(params); 21 | RelativeLayout root = (RelativeLayout) findViewById(R.id.root); 22 | root.addView(curve); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/xlg/util/Util.java: -------------------------------------------------------------------------------- 1 | package com.xlg.util; 2 | 3 | 4 | import com.xlg.beisaierlearn.Log; 5 | 6 | /** 7 | * Created by xulinggang on 15/6/29. 8 | */ 9 | public class Util { 10 | private static final String TAG = "Util"; 11 | public static double calculate3X(double a, double b, double c, double d) { 12 | double A = b*b - 3*a*c; 13 | double B = b*c - 9*a*d; 14 | double C = c*c - 3*b*d; 15 | double D = B * B - 4*A*C; 16 | Log.i(TAG, "a = " + a + ", b = " + b + ", c = " + c + ", d = " + d); 17 | Log.i(TAG,"A = "+A+", B = " + b + ", C = "+C +", D = "+D); 18 | // System.out.println("A ==> "+A); 19 | // System.out.println("B ==> "+B); 20 | // System.out.println("C ==> "+C); 21 | // System.out.println("D ==> "+D); 22 | double result = 0; 23 | if (A == 0 && B == 0) { 24 | //盛金公式1 25 | Log.i(TAG,"盛金公式1"); 26 | result = -3*d/c; 27 | } else if (D > 0) { 28 | //盛金公式2 29 | Log.i(TAG,"盛金公式2"); 30 | //先计算y1,y2 31 | double Y1 = A * b + 3 * a * ((-1*B + Math.sqrt(B * B - 4*A*C))/2); 32 | double Y2 = A * b + 3 * a * ((-1*B - Math.sqrt(B * B - 4*A*C))/2); 33 | Log.i(TAG, "Y1 ==> " + Y1); 34 | Log.i(TAG, "Y2 ==> " + Y2); 35 | 36 | double Y1F = 1d; 37 | double Y2F = 1d; 38 | if (Y1 < 0) { 39 | Y1F = -1d; 40 | } 41 | if (Y2 < 0) { 42 | Y2F = -1d; 43 | } 44 | double bot = 1d/3; 45 | 46 | result = (-b - (Y1F*Math.pow(Y1*Y1F,bot) + Y2F*Math.pow(Y2*Y2F,bot))) / (3*a); 47 | System.out.println("X ==> "+result); 48 | } else if (D == 0) { 49 | Log.i(TAG,"盛金公式3"); 50 | if (A == 0) { 51 | Log.i(TAG,"A == 0"); 52 | return result; 53 | } 54 | double K = B / A; 55 | double X1 = -b/a + K; 56 | double X2 = -K / 2; 57 | System.out.println("X1 ==>"+X1); 58 | System.out.println("X2 ==>"+X2); 59 | double max = Math.max(X1,X2); 60 | if (max >= 0) { 61 | result = max; 62 | } else { 63 | Log.i(TAG, "Max < 0"); 64 | } 65 | } else if (D < 0) { 66 | Log.i(TAG,"盛金公式4"); 67 | if (A <= 0) { 68 | Log.i(TAG, "A < 0"); 69 | return result; 70 | } 71 | double T = (2*A*b - 3*a*B) / (2*Math.sqrt(A*A*A)); 72 | double Z = Math.toDegrees(Math.acos(T)); 73 | Log.i(TAG, "Z ==>"+Z); 74 | double cosZ_3 = Math.cos(Math.toRadians(Z / 3)); 75 | double sinZ_3 = Math.sin(Math.toRadians(Z / 3)); 76 | double X1 = (-b - 2*Math.sqrt(A)*cosZ_3) / (3*a); 77 | double X2 = (-b + Math.sqrt(A)*(cosZ_3 + Math.sqrt(3)*sinZ_3)) / (3*a); 78 | double X3 = (-b + Math.sqrt(A)*(cosZ_3 - Math.sqrt(3)*sinZ_3)) / (3*a); 79 | Log.i(TAG, "X1 ==>" + X1); 80 | Log.i(TAG, "X2 ==>" + X2); 81 | Log.i(TAG, "X3 ==>" + X3); 82 | //因为t是0-1的,所以 83 | if (X1 >= 0 && X1 <= 1) { 84 | result = X1; 85 | } else if (X2 >= 0 && X2 <= 1) { 86 | result = X2; 87 | } else if (X3 >= 0 && X3 <= 1) { 88 | result = X3; 89 | } 90 | } 91 | 92 | return result; 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/xlg/weidget/BeiSaiErView.java: -------------------------------------------------------------------------------- 1 | package com.xlg.weidget; 2 | 3 | import android.animation.TypeEvaluator; 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.PathMeasure; 13 | import android.graphics.PointF; 14 | import android.util.AttributeSet; 15 | import android.util.Log; 16 | import android.view.View; 17 | import android.view.animation.AccelerateInterpolator; 18 | import android.view.animation.LinearInterpolator; 19 | 20 | import com.xlg.beisaierlearn.R; 21 | import com.xlg.util.Util; 22 | 23 | /** 24 | * Created by xulinggang on 15/6/16. 25 | * 主要是模仿苹果的神奇效果,界面往底部缩 26 | * 感觉2种效果都差了点,只能当做是学习贝塞尔曲线绘制,以及drawBitmapMesh 等api得学习了 27 | */ 28 | public class BeiSaiErView extends View { 29 | private static final String TAG = BeiSaiErView.class.getSimpleName(); 30 | private Paint mPaint; 31 | private Context mContext; 32 | private Path mPathLeft; 33 | private Path mPathRight; 34 | 35 | private PointF mLeftStart = new PointF(); 36 | private PointF mLeftControl1 = new PointF(); 37 | private PointF mLeftControl2 = new PointF(); 38 | private PointF mLeftEnd = new PointF(); 39 | 40 | private PointF mRightStart = new PointF(); 41 | private PointF mRightControl1 = new PointF(); 42 | private PointF mRightControl2 = new PointF(); 43 | private PointF mRightEnd = new PointF(); 44 | 45 | private int mViewWide; 46 | private int mViewHeight; 47 | private int mMiddle; 48 | 49 | private Point mPointF; 50 | private Paint mPaintPoint; 51 | 52 | private ValueAnimator mAnimator; 53 | 54 | private Bitmap mBitmap; 55 | 56 | /**显示效果,可选*/ 57 | private Effect mEffect = Effect.EFFECT_ONE; 58 | 59 | public enum Effect { 60 | EFFECT_ONE, EFFECT_TWO 61 | } 62 | 63 | public BeiSaiErView(Context context) { 64 | super(context); 65 | mContext = context; 66 | init(); 67 | 68 | } 69 | 70 | public BeiSaiErView(Context context, AttributeSet attrs) { 71 | super(context, attrs); 72 | init(); 73 | } 74 | 75 | // 76 | private static final int WIDTH = 15, HEIGHT = 22;// 分割数 77 | private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);// 交点数 78 | private float[] matrixOriganal = new float[COUNT * 2];// 基准点坐标数组 79 | private Paint origPaint, movePaint, pointPaint, graphicPaint;// 基准点、变换点和线段的绘制Paint 80 | 81 | private void init() { 82 | mViewWide = getResources().getDisplayMetrics().widthPixels; 83 | mViewHeight = getResources().getDisplayMetrics().heightPixels; 84 | mMiddle = mViewWide / 2; 85 | 86 | mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.bt); 87 | // 初始化坐标数组 88 | int index = 0; 89 | for (int y = 0; y <= HEIGHT; y++) { 90 | float fy = mViewHeight * y / HEIGHT; 91 | 92 | for (int x = 0; x <= WIDTH; x++) { 93 | float fx = mViewWide * x / WIDTH; 94 | setXY(matrixOriganal, index, fx, fy); 95 | index += 1; 96 | } 97 | } 98 | // 实例画笔并设置颜色 99 | origPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 100 | origPaint.setColor(0x660000FF); 101 | 102 | graphicPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 103 | graphicPaint.setStyle(Paint.Style.STROKE); 104 | graphicPaint.setColor(0x660000FF); 105 | graphicPaint.setStrokeWidth(8); 106 | 107 | pointPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 108 | pointPaint.setStyle(Paint.Style.STROKE); 109 | pointPaint.setColor(Color.YELLOW); 110 | pointPaint.setStrokeWidth(6); 111 | 112 | 113 | mPaint = new Paint(); 114 | mPaint.setAntiAlias(true); 115 | mPaint.setStyle(Paint.Style.STROKE); 116 | mPaint.setColor(Color.RED); 117 | mPaint.setStrokeWidth(4); 118 | 119 | mPaintPoint = new Paint(); 120 | mPaintPoint.setAntiAlias(true); 121 | mPaintPoint.setStyle(Paint.Style.STROKE); 122 | mPaintPoint.setColor(Color.YELLOW); 123 | mPaintPoint.setStrokeWidth(8); 124 | 125 | mLeftStart.x = 0; 126 | mLeftStart.y = 0; 127 | mLeftControl1.x = 100; 128 | mLeftControl1.y = 700; 129 | mLeftControl2.x = 300; 130 | mLeftControl2.y = 100; 131 | mLeftEnd.x = mMiddle - 50; 132 | mLeftEnd.y = mViewHeight; 133 | // mLeftEnd.x = 281.1968f; 134 | // mLeftEnd.y = 736.6974f ; 135 | 136 | mRightStart.x = mViewWide; 137 | mRightStart.y = 0; 138 | mRightControl1.x = mViewWide - 100; 139 | mRightControl1.y = 700; 140 | mRightControl2.x = mViewWide - 300; 141 | mRightControl2.y = 100; 142 | mRightEnd.x = mMiddle + 50; 143 | mRightEnd.y = mViewHeight; 144 | 145 | mPathLeft = new Path(); 146 | mPathLeft.moveTo(mLeftStart.x, mLeftStart.y); 147 | mPathLeft.cubicTo(mLeftControl1.x, mLeftControl1.y, mLeftControl2.x, mLeftControl2.y, mLeftEnd.x, mLeftEnd.y); 148 | 149 | mPathRight = new Path(); 150 | mPathRight.moveTo(mRightStart.x, mRightStart.y); 151 | mPathRight.cubicTo(mRightControl1.x, mRightControl1.y, mRightControl2.x, mRightControl2.y, mRightEnd.x, mRightEnd.y); 152 | 153 | initAnimator(); 154 | initInPathPoints(); 155 | } 156 | 157 | /** 158 | * 绘制参考元素 159 | * 160 | * @param canvas 画布 161 | */ 162 | private void drawGuide(Canvas canvas) { 163 | for (int i = 0; i < COUNT * 2; i += 2) { 164 | float x = matrixOriganal[i + 0]; 165 | float y = matrixOriganal[i + 1]; 166 | canvas.drawCircle(x, y, 4, origPaint); 167 | } 168 | } 169 | 170 | /** 171 | * 设置坐标数组 172 | * 173 | * @param array 坐标数组 174 | * @param index 标识值 175 | * @param x x坐标 176 | * @param y y坐标 177 | */ 178 | private void setXY(float[] array, int index, float x, float y) { 179 | array[index * 2 + 0] = x; 180 | array[index * 2 + 1] = y; 181 | } 182 | 183 | 184 | /*** 185 | * 初始化动画,曲线效果 186 | */ 187 | private void initAnimator() { 188 | //两个控制点 189 | // PointF pointF1 = new PointF(mLeftControl1.x); 190 | // PointF pointF2 = new PointF(300, 100); 191 | BeiSaiErEvaluator beiSaiErEvaluator = new BeiSaiErEvaluator(mLeftControl1, mLeftControl2); 192 | 193 | //起点与终点 194 | // PointF start = new PointF(0, 0); 195 | // PointF end = new PointF(mMiddle, mViewHeight); 196 | mAnimator = ValueAnimator.ofObject(beiSaiErEvaluator, mLeftStart, mLeftEnd); 197 | mAnimator.setDuration(3000); 198 | mAnimator.setInterpolator(new LinearInterpolator()); 199 | mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 200 | @Override 201 | public void onAnimationUpdate(ValueAnimator animation) { 202 | mPointF = (Point) animation.getAnimatedValue(); 203 | Log.i(TAG, "mPointF x => " + mPointF.x + ", y => " + mPointF.y + ", t = >" + mPointF.t); 204 | calculateMesh(mPointF); 205 | invalidate(); 206 | } 207 | }); 208 | } 209 | 210 | //计算左右边每段上的点 211 | Point[] posLeft; 212 | Point[] posRight; 213 | float leftLength; 214 | float rightLength; 215 | 216 | private Path cutPath = new Path(); 217 | PathMeasure leftPathMeasure; 218 | PathMeasure rightPathMeasure; 219 | 220 | /** 221 | * 将mesh数组左边界,定位在path上 222 | */ 223 | private void initInPathPoints() { 224 | leftPathMeasure = new PathMeasure(mPathLeft, false); 225 | rightPathMeasure = new PathMeasure(mPathRight, false); 226 | leftLength = leftPathMeasure.getLength(); 227 | float leftDivider = leftLength / HEIGHT; //每段长度 228 | 229 | 230 | posLeft = new Point[HEIGHT + 1]; 231 | posRight = new Point[HEIGHT + 1]; 232 | 233 | float[] f = new float[2]; 234 | for (int i = 0; i < posLeft.length; i++) { 235 | Point pointF = new Point(); 236 | leftPathMeasure.getPosTan(leftDivider * i, f, null); 237 | pointF.x = f[0]; 238 | pointF.y = f[1]; 239 | 240 | CubeParam cubeParam = new CubeParam(); 241 | cubeParam.calculateParams(mLeftStart, mLeftControl1, mLeftControl2, mLeftEnd, pointF); 242 | pointF.t = (float) Util.calculate3X(cubeParam.a, cubeParam.b, cubeParam.c, cubeParam.d); 243 | posLeft[i] = pointF; 244 | 245 | } 246 | 247 | rightLength = leftPathMeasure.getLength(); 248 | float rightDivider = rightLength / HEIGHT; //每段长度 249 | for (int i = 0; i < posRight.length; i++) { 250 | Point pointFR = new Point(); 251 | rightPathMeasure.getPosTan(rightDivider * i, f, null); 252 | pointFR.x = f[0]; 253 | pointFR.y = f[1]; 254 | CubeParam cubeParamR = new CubeParam(); 255 | cubeParamR.calculateParams(mRightStart, mRightControl1, mRightControl2, mRightEnd, pointFR); 256 | pointFR.t = (float) Util.calculate3X(cubeParamR.a, cubeParamR.b, cubeParamR.c, cubeParamR.d); 257 | posRight[i] = pointFR; 258 | } 259 | } 260 | 261 | /** 262 | * 开始动画 263 | */ 264 | public void startRun() { 265 | if (movePath != null) { 266 | movePath.reset(); 267 | } 268 | mAnimator.start(); 269 | } 270 | 271 | 272 | Path movePath = new Path(); 273 | PathMeasure pathMeasure = new PathMeasure(); 274 | 275 | /** 276 | * 重新计算mesh 277 | */ 278 | public void calculateMesh(Point movePoint) { 279 | float leftDivider = 0; 280 | float moveLength = 0; 281 | if (mEffect == Effect.EFFECT_TWO) { 282 | float height = movePoint.y; 283 | //TODO 其实这里应用的是贝塞尔上两点间的曲线长度,而暂时不知道如何求(微积分?),所以用height来替用下 284 | leftPathMeasure.getSegment(0, height, movePath, false); 285 | pathMeasure.setPath(movePath, false); 286 | moveLength = pathMeasure.getLength(); 287 | leftDivider = (leftLength - moveLength) / HEIGHT; //每段长度 288 | } else { 289 | leftDivider = leftLength / HEIGHT; //每段长度 290 | } 291 | 292 | 293 | float[] f = new float[2]; 294 | for (int i = 0; i < posLeft.length; i++) { 295 | Point pointF = new Point(); 296 | if (mEffect == Effect.EFFECT_ONE) { 297 | leftPathMeasure.getPosTan(leftDivider * i, f, null); 298 | } else { 299 | leftPathMeasure.getPosTan(moveLength + leftDivider * i, f, null); 300 | } 301 | pointF.x = f[0]; 302 | pointF.y = f[1]; 303 | 304 | if (mEffect == Effect.EFFECT_ONE) { 305 | CubeParam cubeParam = new CubeParam(); 306 | cubeParam.calculateParams(mLeftStart, mLeftControl1, mLeftControl2, mLeftEnd, pointF); 307 | pointF.t = (float) Util.calculate3X(cubeParam.a, cubeParam.b, cubeParam.c, cubeParam.d); 308 | } 309 | posLeft[i] = pointF; 310 | 311 | } 312 | for (int i = 0; i < posRight.length; i++) { 313 | Point pointFR = new Point(); 314 | rightPathMeasure.getPosTan(moveLength + leftDivider * i, f, null); 315 | pointFR.x = f[0]; 316 | pointFR.y = f[1]; 317 | if (mEffect == Effect.EFFECT_ONE) { 318 | CubeParam cubeParamR = new CubeParam(); 319 | cubeParamR.calculateParams(mRightStart, mRightControl1, mRightControl2, mRightEnd, pointFR); 320 | pointFR.t = (float) Util.calculate3X(cubeParamR.a, cubeParamR.b, cubeParamR.c, cubeParamR.d); 321 | 322 | } 323 | posRight[i] = pointFR; 324 | } 325 | 326 | if (mEffect == Effect.EFFECT_ONE) { 327 | //首先将2条path上的点横坐标移动,根据t 328 | for (int i = 0; i < posLeft.length; i++) { 329 | posLeft[i].x = beiSaiEr3((posLeft[i].t + movePoint.t), mLeftStart.x, mLeftControl1.x, mLeftControl2.x, mLeftEnd.x); 330 | posLeft[i].y = beiSaiEr3((posLeft[i].t + movePoint.t), mLeftStart.y, mLeftControl1.y, mLeftControl2.y, mLeftEnd.y); 331 | } 332 | for (int i = 0; i < posRight.length; i++) { 333 | posRight[i].x = beiSaiEr3((posRight[i].t + movePoint.t), mRightStart.x, mRightControl1.x, mRightControl2.x, mRightEnd.x); 334 | posRight[i].y = beiSaiEr3((posRight[i].t + movePoint.t), mRightStart.y, mRightControl1.y, mRightControl2.y, mRightEnd.y); 335 | } 336 | 337 | } 338 | 339 | int index = 0; 340 | for (int i = 0; i <= HEIGHT; i++) { 341 | float fy = posLeft[i].y; 342 | 343 | float distance = posRight[i].x - posLeft[i].x; 344 | 345 | float pad = distance / WIDTH; 346 | float margin = posLeft[i].x; 347 | for (int x = 0; x <= WIDTH; x++) { 348 | float fx = margin + pad * x; 349 | setXY(matrixOriganal, index, fx, fy); 350 | index += 1; 351 | } 352 | } 353 | } 354 | 355 | /** 356 | * 求3阶贝塞尔值 357 | * 358 | * @param t (0 - 1) 359 | * @param start 起始点 360 | * @param control1 控制点1 361 | * @param control2 控制点2 362 | * @param end 结束点 363 | * @return 364 | */ 365 | private float beiSaiEr3(float t, float start, float control1, float control2, float end) { 366 | float result = 0; 367 | if (t >= 1f) { 368 | t = 1f; 369 | } 370 | float timeLeft = 1 - t; 371 | result = start * timeLeft * timeLeft * timeLeft + 372 | 3 * control1 * t * timeLeft * timeLeft + 373 | 3 * control2 * t * t * timeLeft + 374 | end * t * t * t; 375 | 376 | return result; 377 | 378 | } 379 | 380 | 381 | @Override 382 | protected void onDraw(Canvas canvas) { 383 | super.onDraw(canvas); 384 | canvas.drawColor(Color.BLACK); 385 | canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, matrixOriganal, 0, null, 0, null); 386 | drawGuide(canvas); 387 | // 388 | canvas.drawPath(mPathLeft, mPaint); 389 | canvas.drawPath(mPathRight, mPaint); 390 | canvas.drawPoint(0, 700, mPaint); 391 | // 392 | if (mPointF != null) { 393 | canvas.drawLine(mPointF.x, mPointF.y, mViewWide - mPointF.x, mPointF.y, mPaintPoint); 394 | } 395 | // 396 | for (int i = 0; i < posLeft.length; i++) { 397 | canvas.drawPoint(posLeft[i].x, posLeft[i].y, pointPaint); 398 | } 399 | 400 | } 401 | 402 | @Override 403 | protected void onDetachedFromWindow() { 404 | super.onDetachedFromWindow(); 405 | } 406 | 407 | /** 408 | * 属性动画差值器 409 | */ 410 | class BeiSaiErEvaluator implements TypeEvaluator { 411 | private PointF pointF1; //控制点1 412 | private PointF pointF2; //控制点2 413 | 414 | public BeiSaiErEvaluator(PointF pointF1, PointF pointF2) { 415 | this.pointF1 = pointF1; 416 | this.pointF2 = pointF2; 417 | } 418 | 419 | 420 | @Override 421 | public PointF evaluate(float fraction, PointF startValue, PointF endValue) { 422 | float timeLeft = 1 - fraction; 423 | Point pointF = new Point(); 424 | pointF.t = fraction; 425 | 426 | //代入公式 427 | pointF.x = startValue.x * timeLeft * timeLeft * timeLeft + 428 | 3 * pointF1.x * fraction * timeLeft * timeLeft + 429 | 3 * pointF2.x * fraction * fraction * timeLeft + 430 | endValue.x * fraction * fraction * fraction; 431 | 432 | pointF.y = startValue.y * timeLeft * timeLeft * timeLeft + 433 | 3 * pointF1.y * fraction * timeLeft * timeLeft + 434 | 3 * pointF2.y * fraction * fraction * timeLeft + 435 | endValue.y * fraction * fraction * fraction; 436 | return pointF; 437 | } 438 | } 439 | 440 | class Point extends PointF { 441 | /** 442 | * 该点在贝塞尔曲线上的时间 443 | */ 444 | public float t; 445 | } 446 | 447 | /** 448 | * 一元3次方程,各参数计算 449 | */ 450 | class CubeParam { 451 | public double a; 452 | public double b; 453 | public double c; 454 | public double d; 455 | 456 | //a = -X0 + 3X1 - 3X2 + X3 457 | //b = 3X0 - 6X1 + 3X2 458 | //c = -3X0 + 3X1 459 | //d = X0 - Bx(已知的坐标) 460 | 461 | /** 462 | * @param f0 起始点 463 | * @param f1 控制点1 464 | * @param f2 控制点2 465 | * @param f3 结束点 466 | * @param fp 曲线上的点 467 | */ 468 | public void calculateParams(PointF f0, PointF f1, PointF f2, PointF f3, PointF fp) { 469 | a = -1 * f0.x + 3 * f1.x - 3 * f2.x + f3.x; 470 | b = 3 * f0.x - 6 * f1.x + 3 * f2.x; 471 | c = -3 * f0.x + 3 * f1.x; 472 | d = f0.x - fp.x; 473 | } 474 | } 475 | 476 | 477 | } 478 | -------------------------------------------------------------------------------- /app/src/main/java/com/xlg/weidget/BitmapMeshView2.java: -------------------------------------------------------------------------------- 1 | package com.xlg.weidget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.util.AttributeSet; 9 | import android.view.MotionEvent; 10 | import android.view.View; 11 | 12 | import com.xlg.beisaierlearn.R; 13 | 14 | 15 | /** 16 | * Created by xulinggang on 15/6/26. 17 | */ 18 | public class BitmapMeshView2 extends View { 19 | private static final int WIDTH = 9, HEIGHT = 9;// 分割数 20 | private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);// 交点数 21 | 22 | private Bitmap mBitmap;// 位图对象 23 | 24 | private float[] matrixOriganal = new float[COUNT * 2];// 基准点坐标数组 25 | private float[] matrixMoved = new float[COUNT * 2];// 变换后点坐标数组 26 | 27 | private float clickX, clickY;// 触摸屏幕时手指的xy坐标 28 | 29 | private Paint origPaint, movePaint, linePaint;// 基准点、变换点和线段的绘制Paint 30 | 31 | public BitmapMeshView2(Context context, AttributeSet attrs) { 32 | super(context, attrs); 33 | setFocusable(true); 34 | 35 | // 实例画笔并设置颜色 36 | origPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 37 | origPaint.setColor(0x660000FF); 38 | movePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 39 | movePaint.setColor(0x99FF0000); 40 | linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 41 | linePaint.setColor(0xFFFFFB00); 42 | 43 | // 获取位图资源 44 | mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.bt); 45 | 46 | // 初始化坐标数组 47 | int index = 0; 48 | for (int y = 0; y <= HEIGHT; y++) { 49 | float fy = mBitmap.getHeight() * y / HEIGHT; 50 | 51 | for (int x = 0; x <= WIDTH; x++) { 52 | float fx = mBitmap.getWidth() * x / WIDTH; 53 | setXY(matrixMoved, index, fx, fy); 54 | setXY(matrixOriganal, index, fx, fy); 55 | index += 1; 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * 设置坐标数组 62 | * 63 | * @param array 64 | * 坐标数组 65 | * @param index 66 | * 标识值 67 | * @param x 68 | * x坐标 69 | * @param y 70 | * y坐标 71 | */ 72 | private void setXY(float[] array, int index, float x, float y) { 73 | array[index * 2 + 0] = x; 74 | array[index * 2 + 1] = y; 75 | } 76 | 77 | @Override 78 | protected void onDraw(Canvas canvas) { 79 | 80 | // 绘制网格位图 81 | canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, matrixMoved, 0, null, 0, null); 82 | 83 | // 绘制参考元素 84 | drawGuide(canvas); 85 | } 86 | 87 | /** 88 | * 绘制参考元素 89 | * 90 | * @param canvas 91 | * 画布 92 | */ 93 | private void drawGuide(Canvas canvas) { 94 | for (int i = 0; i < COUNT * 2; i += 2) { 95 | float x = matrixOriganal[i + 0]; 96 | float y = matrixOriganal[i + 1]; 97 | canvas.drawCircle(x, y, 4, origPaint); 98 | 99 | float x1 = matrixOriganal[i + 0]; 100 | float y1 = matrixOriganal[i + 1]; 101 | float x2 = matrixMoved[i + 0]; 102 | float y2 = matrixMoved[i + 1]; 103 | canvas.drawLine(x1, y1, x2, y2, origPaint); 104 | } 105 | 106 | for (int i = 0; i < COUNT * 2; i += 2) { 107 | float x = matrixMoved[i + 0]; 108 | float y = matrixMoved[i + 1]; 109 | canvas.drawCircle(x, y, 4, movePaint); 110 | } 111 | 112 | canvas.drawCircle(clickX, clickY, 6, linePaint); 113 | } 114 | 115 | /** 116 | * 计算变换数组坐标 117 | */ 118 | private void smudge() { 119 | for (int i = 0; i < COUNT * 2; i += 2) { 120 | 121 | float xOriginal = matrixOriganal[i + 0]; 122 | float yOriginal = matrixOriganal[i + 1]; 123 | 124 | float dist_click_to_origin_x = clickX - xOriginal; 125 | float dist_click_to_origin_y = clickY - yOriginal; 126 | 127 | float kv_kat = dist_click_to_origin_x * dist_click_to_origin_x + dist_click_to_origin_y * dist_click_to_origin_y; 128 | 129 | float pull = (float) (1000000 / kv_kat / Math.sqrt(kv_kat)); 130 | 131 | if (pull >= 1) { 132 | matrixMoved[i + 0] = clickX; 133 | matrixMoved[i + 1] = clickY; 134 | } else { 135 | matrixMoved[i + 0] = xOriginal + dist_click_to_origin_x * pull; 136 | matrixMoved[i + 1] = yOriginal + dist_click_to_origin_y * pull; 137 | } 138 | } 139 | } 140 | 141 | @Override 142 | public boolean onTouchEvent(MotionEvent event) { 143 | clickX = event.getX(); 144 | clickY = event.getY(); 145 | smudge(); 146 | invalidate(); 147 | return true; 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/xlg/weidget/DrawDottedCurve.java: -------------------------------------------------------------------------------- 1 | package com.xlg.weidget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.DashPathEffect; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.PathMeasure; 10 | import android.os.CountDownTimer; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.View; 14 | 15 | import java.util.Random; 16 | 17 | public class DrawDottedCurve extends View { 18 | 19 | Path[] path = new Path[8]; 20 | Path[] drawingPath = new Path[8]; 21 | PathMeasure[] measure = new PathMeasure[8]; 22 | Path[] segmentPath = new Path[8]; 23 | float[] length = new float[8]; 24 | float[] start = new float[8]; 25 | float[] percent = new float[8]; 26 | Paint paint = new Paint(); 27 | float x1; 28 | float y1; 29 | float x3; 30 | float y3; 31 | long k = 0; 32 | Canvas canvas; 33 | Random r = new Random(); 34 | 35 | // public DrawDottedCurve(Context context, AttributeSet attrs) { 36 | // super(context, attrs); 37 | // } 38 | 39 | public DrawDottedCurve(Context context, int a, int b, int c, int d) { 40 | super(context); 41 | x1 = a; 42 | y1 = b; 43 | x3 = c; 44 | y3 = d; 45 | 46 | paint.setAlpha(255); 47 | paint.setStrokeWidth(2); 48 | paint.setColor(Color.RED); 49 | paint.setStyle(Paint.Style.STROKE); 50 | paint.setPathEffect(new DashPathEffect(new float[] { 2, 4 }, 50)); 51 | 52 | for (int i = 0; i < 8; i++) { 53 | k = r.nextInt(100 - 30) + 30; 54 | path[i] = new Path(); 55 | path[i].moveTo(x1 + k, y1 + k); // 56 | path[i].quadTo((x3 + k - x1 + k) / 2, (y3 + k - y1 + k) / 2, 57 | x3 + k, y3 + k); // Calculate Bezier Curves 58 | } 59 | 60 | final long DRAW_TIME = 10000; 61 | CountDownTimer timer = new CountDownTimer(DRAW_TIME, 100) { 62 | 63 | @Override 64 | public void onTick(long millisUntilFinished) { 65 | Log.d("Timer", "Inizio"); 66 | for (int i = 0; i < 8; i++) { 67 | start[i] = 0; 68 | measure[i] = new PathMeasure(); 69 | measure[i].setPath(path[i], false); 70 | 71 | percent[i] = ((float) (DRAW_TIME - millisUntilFinished)) 72 | / (float) DRAW_TIME; 73 | 74 | segmentPath[i] = new Path(); 75 | drawingPath[i] = new Path(); 76 | length[i] = measure[i].getLength() * percent[i]; 77 | measure[i].getSegment(start[i], length[i], segmentPath[i], 78 | true); 79 | start[i] = length[i]; 80 | drawingPath[i].addPath(segmentPath[i]); 81 | 82 | 83 | } 84 | invalidate(); 85 | } 86 | @Override 87 | public void onFinish() { 88 | for (int i = 0; i < 8; i++) { 89 | measure[i].getSegment(start[i], measure[i].getLength(), 90 | segmentPath[i], true); 91 | drawingPath[i].addPath(segmentPath[i]); 92 | 93 | 94 | } 95 | 96 | invalidate(); 97 | Log.d("Timer", "Fine"); 98 | } 99 | 100 | }; 101 | timer.start(); 102 | } 103 | @Override 104 | public void onDraw(Canvas canvas) { 105 | super.onDraw(canvas); 106 | for (int i = 0; i < 8; i++) { 107 | canvas.drawPath(drawingPath[i], paint); 108 | invalidate(); 109 | 110 | } 111 | 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /app/src/main/java/com/xlg/weidget/HeartFlowLayout.java: -------------------------------------------------------------------------------- 1 | package com.xlg.weidget; 2 | 3 | import android.animation.Animator; 4 | import android.animation.TypeEvaluator; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.graphics.PointF; 8 | import android.util.AttributeSet; 9 | import android.view.ViewGroup; 10 | import android.view.animation.AnimationSet; 11 | import android.view.animation.LinearInterpolator; 12 | import android.widget.ImageView; 13 | import android.widget.RelativeLayout; 14 | 15 | 16 | import com.xlg.beisaierlearn.R; 17 | 18 | import java.util.Random; 19 | 20 | /** 21 | * Created by xulinggang on 15/6/21. 22 | */ 23 | public class HeartFlowLayout extends RelativeLayout { 24 | private Context mContext; 25 | private Random mRandom = new Random(); 26 | private static final int[] mDrawables = new int[]{ 27 | R.mipmap.heart1, 28 | R.mipmap.heart2, 29 | R.mipmap.heart3, 30 | R.mipmap.heart4, 31 | }; 32 | public HeartFlowLayout(Context context) { 33 | super(context); 34 | mContext = context; 35 | } 36 | 37 | public HeartFlowLayout(Context context, AttributeSet attrs) { 38 | super(context, attrs); 39 | mContext = context; 40 | } 41 | 42 | private int mWide; 43 | private int mHeight; 44 | private int mMiddle; 45 | @Override 46 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 47 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 48 | mWide = getMeasuredWidth(); 49 | mHeight = getMeasuredHeight(); 50 | mMiddle = mWide / 2; 51 | } 52 | 53 | private PointF getPointF(int scale) { 54 | 55 | PointF pointF = new PointF(); 56 | pointF.x = mRandom.nextInt((mWide - 100));//减去100 是为了控制 x轴活动范围,看效果 随意~~ 57 | //再Y轴上 为了确保第二个点 在第一个点之上,我把Y分成了上下两半 这样动画效果好一些 也可以用其他方法 58 | pointF.y = mRandom.nextInt((mHeight - 100))/scale; 59 | return pointF; 60 | } 61 | 62 | public void addHeart(){ 63 | final ImageView imageView = new ImageView(mContext); 64 | imageView.setImageResource(mDrawables[mRandom.nextInt(4)]); 65 | LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); 66 | params.addRule(ALIGN_PARENT_BOTTOM,TRUE); 67 | params.addRule(CENTER_HORIZONTAL,TRUE); 68 | imageView.setLayoutParams(params); 69 | addView(imageView); 70 | 71 | PointF pointF1 = new PointF(mMiddle - 50,mHeight - 200); 72 | PointF pointF2 = new PointF(mMiddle + 100,mHeight - 320); 73 | //new PointF((mWidth-dWidth)/2,mHeight-dHeight),new PointF(random.nextInt(getWidth()),0));//随机 74 | PointF pointF0 = new PointF(0,mHeight); 75 | PointF pointF3 = new PointF( 300,0); 76 | 77 | ValueAnimator animator = ValueAnimator.ofObject(new BezierEvalutor(pointF1,pointF2),pointF0,pointF3); 78 | animator.setDuration(3000); 79 | animator.setTarget(imageView); 80 | animator.setInterpolator(new LinearInterpolator()); 81 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 82 | @Override 83 | public void onAnimationUpdate(ValueAnimator animation) { 84 | PointF pointF = (PointF) animation.getAnimatedValue(); 85 | imageView.setX(pointF.x); 86 | imageView.setY(pointF.y); 87 | } 88 | }); 89 | animator.addListener(new Animator.AnimatorListener() { 90 | @Override 91 | public void onAnimationStart(Animator animation) { 92 | 93 | } 94 | 95 | @Override 96 | public void onAnimationEnd(Animator animation) { 97 | removeView(imageView); 98 | } 99 | 100 | @Override 101 | public void onAnimationCancel(Animator animation) { 102 | 103 | } 104 | 105 | @Override 106 | public void onAnimationRepeat(Animator animation) { 107 | 108 | } 109 | }); 110 | animator.start(); 111 | 112 | 113 | } 114 | 115 | class BezierEvalutor implements TypeEvaluator{ 116 | //B(t) = P0 * (1-t)^3 + 3 * P1 * t(1-t)^2 + 3 * P2 * t^2(1-t) + P3 * t^3 117 | 118 | //中间两个控制点 119 | private PointF pointF1; 120 | private PointF pointF2; 121 | 122 | public BezierEvalutor(PointF pointF1, PointF pointF2) { 123 | this.pointF1 = pointF1; 124 | this.pointF2 = pointF2; 125 | } 126 | 127 | 128 | @Override 129 | public PointF evaluate(float fraction, PointF startValue, PointF endValue) { 130 | float timeLeft = 1.0f - fraction; 131 | PointF pointF = new PointF(); 132 | 133 | //代入公式 134 | pointF.x = startValue.x * timeLeft * timeLeft * timeLeft + 135 | 3 * pointF1.x * fraction * timeLeft * timeLeft + 136 | 3 * pointF2.x * fraction * fraction * timeLeft + 137 | endValue.x + fraction * fraction * fraction; 138 | 139 | pointF.y = startValue.y * timeLeft * timeLeft * timeLeft + 140 | 3 * pointF1.y * fraction * timeLeft * timeLeft + 141 | 3 * pointF2.y * fraction * fraction * timeLeft + 142 | endValue.y + fraction * fraction * fraction; 143 | 144 | return pointF; 145 | } 146 | } 147 | 148 | 149 | 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/xlg/weidget/PathMeasureView.java: -------------------------------------------------------------------------------- 1 | package com.xlg.weidget; 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.graphics.PathEffect; 9 | import android.graphics.PathMeasure; 10 | import android.graphics.PointF; 11 | import android.graphics.RectF; 12 | import android.transition.PathMotion; 13 | import android.util.AttributeSet; 14 | import android.util.Log; 15 | import android.view.View; 16 | import android.view.animation.PathInterpolator; 17 | 18 | /** 19 | * Created by xulinggang on 15/6/28. 20 | */ 21 | public class PathMeasureView extends View { 22 | private static final String TAG = "PathMeasureView"; 23 | private int mViewWide; 24 | private int mViewHeight; 25 | private float radius; 26 | public PathMeasureView(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | } 29 | 30 | @Override 31 | protected void onFinishInflate() { 32 | super.onFinishInflate(); 33 | } 34 | 35 | 36 | 37 | @Override 38 | protected void onDraw(Canvas canvas) { 39 | super.onDraw(canvas); 40 | canvas.drawColor(Color.BLACK); 41 | drawPathCircle(canvas); 42 | } 43 | 44 | 45 | @Override 46 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 47 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 48 | mViewWide = getMeasuredWidth(); 49 | mViewHeight = getMeasuredHeight(); 50 | radius = 200; 51 | Log.i(TAG, "wide = " + mViewWide + ", mViewHeight = " + mViewHeight); 52 | } 53 | 54 | private void drawPathCircle(Canvas canvas) { 55 | PointF center = new PointF(mViewWide / 2, mViewHeight / 2); 56 | RectF area = new RectF(center.x - radius,center.y - radius, center.x + radius, center.y + radius); 57 | Path orbit = new Path(); 58 | orbit.addArc(area, 180, 90); 59 | 60 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 61 | paint.setStrokeWidth(6); 62 | paint.setStyle(Paint.Style.STROKE); 63 | paint.setColor(Color.RED); 64 | canvas.drawPath(orbit, paint); 65 | 66 | Paint paintPoint = new Paint(Paint.ANTI_ALIAS_FLAG); 67 | paintPoint.setStrokeWidth(6); 68 | paintPoint.setStyle(Paint.Style.STROKE); 69 | paintPoint.setColor(Color.WHITE); 70 | canvas.drawPoint(center.x, center.y, paintPoint); 71 | 72 | // PathInterpolator pathInterpolator = new PathInterpolator() 73 | // PathEffect 74 | PathMeasure pathMeasure = new PathMeasure(orbit,false); 75 | float length = pathMeasure.getLength(); 76 | Log.i(TAG,"length => " + length); 77 | float divider = length / 4; 78 | float[] position = new float[2]; 79 | for (int i = 0; i <= 4; i++) { 80 | pathMeasure.getPosTan(divider * i,position , null); 81 | canvas.drawPoint(position[0],position[1],paintPoint); 82 | } 83 | 84 | 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 13 | 14 |