├── README.md └── TestMockAlipay ├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── alipay ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mock │ │ └── alipay │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mock │ │ │ └── alipay │ │ │ ├── Callback.java │ │ │ ├── PasswordKeypad.java │ │ │ └── view │ │ │ ├── MDProgressBar.java │ │ │ ├── PasswordKeyboard.java │ │ │ └── PasswordView.java │ └── res │ │ ├── anim │ │ ├── bottom_in.xml │ │ └── bottom_out.xml │ │ ├── drawable │ │ ├── key_normal.9.png │ │ ├── key_press.9.png │ │ └── key_selector.xml │ │ ├── layout │ │ └── password_keypad.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── mock │ └── alipay │ └── ExampleUnitTest.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── testmockalipay │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── testmockalipay │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_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 │ └── test │ └── java │ └── com │ └── example │ └── testmockalipay │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | # ImitateAlipayPasswordInput 2 | 参考支付宝密码输入对话框设计,已经封装成Android Library,导入即可使用。 3 | ##效果图 4 | ![密码输入框效果图](http://upload-images.jianshu.io/upload_images/1743063-a16c1bf59caf5d41.gif?imageMogr2/auto-orient/strip) 5 | #欢迎大家在此基础上再次完善功能,提高用户体验。 6 | -------------------------------------------------------------------------------- /TestMockAlipay/.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 | -------------------------------------------------------------------------------- /TestMockAlipay/.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 | -------------------------------------------------------------------------------- /TestMockAlipay/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /TestMockAlipay/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TestMockAlipay/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /TestMockAlipay/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | -------------------------------------------------------------------------------- /TestMockAlipay/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /TestMockAlipay/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /TestMockAlipay/alipay/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /TestMockAlipay/alipay/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:23.4.0' 30 | testCompile 'junit:junit:4.12' 31 | } 32 | -------------------------------------------------------------------------------- /TestMockAlipay/alipay/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/chenjiawei/Downloads/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /TestMockAlipay/alipay/src/androidTest/java/com/mock/alipay/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.mock.alipay; 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.mock.alipay.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TestMockAlipay/alipay/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /TestMockAlipay/alipay/src/main/java/com/mock/alipay/Callback.java: -------------------------------------------------------------------------------- 1 | package com.mock.alipay; 2 | 3 | /** 4 | * Created by chenjiawei on 16/8/26. 5 | */ 6 | public interface Callback { 7 | 8 | void onForgetPassword(); 9 | 10 | void onInputCompleted(CharSequence password); 11 | 12 | void onPasswordCorrectly(); 13 | 14 | void onCancel(); 15 | } 16 | -------------------------------------------------------------------------------- /TestMockAlipay/alipay/src/main/java/com/mock/alipay/PasswordKeypad.java: -------------------------------------------------------------------------------- 1 | package com.mock.alipay; 2 | 3 | import android.app.Activity; 4 | import android.content.DialogInterface; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.DialogFragment; 9 | import android.text.TextUtils; 10 | import android.util.DisplayMetrics; 11 | import android.view.Gravity; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.view.Window; 16 | import android.widget.RelativeLayout; 17 | import android.widget.TextView; 18 | 19 | import com.mock.alipay.view.MDProgressBar; 20 | import com.mock.alipay.view.PasswordKeyboard; 21 | import com.mock.alipay.view.PasswordView; 22 | 23 | 24 | /** 25 | * Created by chenjiawei on 16/8/30. 26 | * 防支付宝密码输入界面 27 | */ 28 | public class PasswordKeypad extends DialogFragment implements View.OnClickListener, PasswordKeyboard.OnPasswordInputListener, 29 | MDProgressBar.OnPasswordCorrectlyListener { 30 | 31 | private TextView errorMsgTv; 32 | 33 | private Callback mCallback; 34 | 35 | private RelativeLayout passwordContainer; 36 | 37 | private MDProgressBar progressBar; 38 | 39 | private PasswordView passwordView; 40 | 41 | private int passwordCount; 42 | 43 | private boolean passwordState = true; 44 | 45 | PasswordKeyboard numberKeyBoard; 46 | 47 | private StringBuffer mPasswordBuffer = new StringBuffer(); 48 | 49 | @Override 50 | public void onAttach(Activity context) { 51 | super.onAttach(context); 52 | if (context instanceof Callback) { 53 | mCallback = (Callback) context; 54 | } 55 | } 56 | 57 | @Nullable 58 | @Override 59 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 60 | return inflater.inflate(R.layout.password_keypad, container, false); 61 | } 62 | 63 | @Override 64 | public void onCreate(Bundle savedInstanceState) { 65 | super.onCreate(savedInstanceState); 66 | setStyle(DialogFragment.STYLE_NO_TITLE, 0); 67 | } 68 | 69 | @Override 70 | public void onStart() { 71 | super.onStart(); 72 | DisplayMetrics dm = new DisplayMetrics(); 73 | getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm); 74 | Window window = getDialog().getWindow(); 75 | //去掉边框 76 | window.setBackgroundDrawable(new ColorDrawable(0xffffffff)); 77 | window.setLayout(dm.widthPixels, window.getAttributes().height); 78 | window.setWindowAnimations(R.style.exist_menu_animstyle); 79 | window.setGravity(Gravity.BOTTOM); 80 | } 81 | 82 | @Override 83 | public void onViewCreated(View view, Bundle savedInstanceState) { 84 | super.onViewCreated(view, savedInstanceState); 85 | errorMsgTv = (TextView) view.findViewById(R.id.error_msg); 86 | TextView forgetPasswordTv = (TextView) view.findViewById(R.id.forget_password); 87 | TextView cancelTv = (TextView) view.findViewById(R.id.cancel_dialog); 88 | 89 | passwordContainer = (RelativeLayout) view.findViewById(R.id.password_content); 90 | progressBar = (MDProgressBar) view.findViewById(R.id.password_progressBar); 91 | progressBar.setOnPasswordCorrectlyListener(this); 92 | passwordView = (PasswordView) view.findViewById(R.id.password_inputBox); 93 | //设置密码长度 94 | if (passwordCount > 0) { 95 | passwordView.setPasswordCount(passwordCount); 96 | } 97 | 98 | numberKeyBoard = (PasswordKeyboard) view.findViewById(R.id.password_keyboard); 99 | numberKeyBoard.setOnPasswordInputListener(this); 100 | 101 | cancelTv.setOnClickListener(this); 102 | forgetPasswordTv.setOnClickListener(this); 103 | } 104 | 105 | /** 106 | * 设置密码长度 107 | */ 108 | public void setPasswordCount(int passwordCount) { 109 | this.passwordCount = passwordCount; 110 | } 111 | 112 | @Override 113 | public void onClick(View v) { 114 | if (R.id.cancel_dialog == v.getId()) { 115 | if (mCallback != null) { 116 | mCallback.onCancel(); 117 | } 118 | dismiss(); 119 | } else if (R.id.forget_password == v.getId()) { 120 | if (mCallback != null) { 121 | mCallback.onForgetPassword(); 122 | } 123 | } 124 | } 125 | 126 | public void setCallback(Callback callBack) { 127 | this.mCallback = callBack; 128 | } 129 | 130 | public void setPasswordState(boolean correct) { 131 | setPasswordState(correct, ""); 132 | } 133 | 134 | public void setPasswordState(boolean correct, String msg) { 135 | passwordState = correct; 136 | if (correct) { 137 | progressBar.setSuccessfullyStatus(); 138 | } else { 139 | numberKeyBoard.resetKeyboard(); 140 | passwordView.clearPassword(); 141 | progressBar.setVisibility(View.GONE); 142 | passwordContainer.setVisibility(View.VISIBLE); 143 | errorMsgTv.setText(msg); 144 | } 145 | } 146 | 147 | @Override 148 | public void onPasswordCorrectly() { 149 | if (mCallback != null) { 150 | mCallback.onPasswordCorrectly(); 151 | } 152 | } 153 | 154 | private void startLoading(CharSequence password) { 155 | passwordContainer.setVisibility(View.INVISIBLE); 156 | progressBar.setVisibility(View.VISIBLE); 157 | if (mCallback != null) { 158 | mCallback.onInputCompleted(password); 159 | } 160 | } 161 | 162 | @Override 163 | public void onInput(String character) { 164 | if (PasswordKeyboard.DEL.equals(character)) { 165 | if (mPasswordBuffer.length() > 0) { 166 | mPasswordBuffer.delete(mPasswordBuffer.length() - 1, mPasswordBuffer.length()); 167 | } 168 | } else if (PasswordKeyboard.DONE.equals(character)) { 169 | dismiss(); 170 | } else { 171 | if (!passwordState) { 172 | if (!TextUtils.isEmpty(errorMsgTv.getText())) { 173 | errorMsgTv.setText(""); 174 | } 175 | } 176 | mPasswordBuffer.append(character); 177 | } 178 | passwordView.setPassword(mPasswordBuffer); 179 | if (mPasswordBuffer.length() == passwordView.getPasswordCount()) { 180 | startLoading(mPasswordBuffer); 181 | } 182 | } 183 | 184 | @Override 185 | public void onDismiss(DialogInterface dialog) { 186 | super.onDismiss(dialog); 187 | if (mPasswordBuffer.length() > 0) { 188 | mPasswordBuffer.delete(0, mPasswordBuffer.length()); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /TestMockAlipay/alipay/src/main/java/com/mock/alipay/view/MDProgressBar.java: -------------------------------------------------------------------------------- 1 | package com.mock.alipay.view; 2 | 3 | import android.animation.Animator; 4 | import android.animation.Animator.AnimatorListener; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ValueAnimator; 7 | import android.animation.ValueAnimator.AnimatorUpdateListener; 8 | import android.content.Context; 9 | import android.content.res.TypedArray; 10 | import android.graphics.Canvas; 11 | import android.graphics.Color; 12 | import android.graphics.Paint; 13 | import android.graphics.RectF; 14 | import android.util.AttributeSet; 15 | import android.view.View; 16 | import android.view.animation.Animation; 17 | import android.view.animation.DecelerateInterpolator; 18 | import android.view.animation.LinearInterpolator; 19 | import android.view.animation.Transformation; 20 | 21 | import com.mock.alipay.R; 22 | 23 | 24 | /** 25 | * Created by chenjiawei on 16/10/28. 26 | */ 27 | public class MDProgressBar extends View { 28 | 29 | private final static String TAG = MDProgressBar.class.getSimpleName(); 30 | 31 | private static final float DEFAULT_MAX_ANGLE = -305f; 32 | 33 | private static final float DEFAULT_MIN_ANGLE = -19f; 34 | 35 | //默认的动画时间 36 | private static final int DEFAULT_DURATION = 660; 37 | 38 | private final static int DEFAULT_ARC_COLOR = Color.BLUE; 39 | //圆弧颜色 40 | private int arcColor = DEFAULT_ARC_COLOR; 41 | 42 | private AnimatorSet animatorSet; 43 | 44 | private float mBorderWidth; 45 | 46 | private Paint mPaint; 47 | 48 | private RectF arcRectF; 49 | 50 | private float startAngle = -45f; 51 | 52 | private float sweepAngle = -19f; 53 | 54 | private float incrementAngele = 0; 55 | //是否需要开始绘制对勾 56 | private boolean isNeedTick = false; 57 | 58 | private int mResize; 59 | 60 | private TickAnimation mTickAnimation; 61 | //判断"对勾"动画是否过半,"对勾"由两条线绘制而成。 62 | private boolean isAnimationOverHalf = false; 63 | //圆形进度条的半径 64 | private float mRadius; 65 | 66 | private float startY1; 67 | 68 | private float startX1; 69 | 70 | private float stopX1; 71 | 72 | private float stopY1; 73 | 74 | private float stopX2; 75 | 76 | private float stopY2; 77 | 78 | private OnPasswordCorrectlyListener mListener; 79 | 80 | public MDProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { 81 | super(context, attrs, defStyleAttr); 82 | init(context, attrs); 83 | } 84 | 85 | public MDProgressBar(Context context, AttributeSet attrs) { 86 | super(context, attrs); 87 | init(context, attrs); 88 | } 89 | 90 | private void init(Context context, AttributeSet attrs) { 91 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.materialStatusProgressAttr); 92 | arcColor = typedArray.getColor(R.styleable.materialStatusProgressAttr_arcColor, Color.parseColor("#4a90e2")); 93 | mBorderWidth = typedArray.getDimension(R.styleable.materialStatusProgressAttr_progressBarBorderWidth, 94 | getResources().getDimension(R.dimen.material_status_progress_border)); 95 | typedArray.recycle(); 96 | mPaint = new Paint(); 97 | mPaint.setColor(arcColor); 98 | mPaint.setStrokeWidth(mBorderWidth); 99 | mPaint.setAntiAlias(true); 100 | mPaint.setStyle(Paint.Style.STROKE); 101 | arcRectF = new RectF(); 102 | mTickAnimation = new TickAnimation(); 103 | mTickAnimation.setDuration(800); 104 | //对勾动画监听 105 | mTickAnimation.setAnimationListener(new Animation.AnimationListener() { 106 | @Override 107 | public void onAnimationStart(Animation animation) { 108 | 109 | } 110 | 111 | @Override 112 | public void onAnimationEnd(Animation animation) { 113 | //当对勾动画完成后,延迟一秒回掉,不然动画效果不明显 114 | if (mListener != null) { 115 | postDelayed(new Runnable() { 116 | @Override 117 | public void run() { 118 | mListener.onPasswordCorrectly(); 119 | 120 | } 121 | }, 1000); 122 | } 123 | } 124 | 125 | @Override 126 | public void onAnimationRepeat(Animation animation) { 127 | 128 | } 129 | }); 130 | } 131 | 132 | private void arcPaint() { 133 | mPaint.reset(); 134 | mPaint.setColor(arcColor); 135 | mPaint.setStrokeWidth(mBorderWidth); 136 | mPaint.setAntiAlias(true); 137 | mPaint.setStyle(Paint.Style.STROKE); 138 | } 139 | 140 | private void linePaint() { 141 | mPaint.reset(); 142 | mPaint.setColor(arcColor); 143 | mPaint.setStrokeWidth(mBorderWidth); 144 | mPaint.setAntiAlias(true); 145 | } 146 | //对勾动画完成回调 147 | public void setOnPasswordCorrectlyListener(OnPasswordCorrectlyListener listener) { 148 | this.mListener = listener; 149 | } 150 | 151 | @Override 152 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 153 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 154 | startY1 = getMeasuredHeight() / 2; 155 | mRadius = getMeasuredHeight() / 2 - 2 * mBorderWidth; 156 | startX1 = startY1 - getMeasuredHeight() / 5; 157 | } 158 | 159 | @Override 160 | protected void onDraw(Canvas canvas) { 161 | super.onDraw(canvas); 162 | arcPaint(); 163 | canvas.drawArc(arcRectF, startAngle + incrementAngele, sweepAngle, false, mPaint); 164 | if (animatorSet == null || !animatorSet.isRunning() && !isNeedTick) { 165 | startAnimation(); 166 | } 167 | if (isNeedTick) { 168 | //补全圆 169 | arcPaint(); 170 | canvas.drawArc(arcRectF, startAngle + incrementAngele + sweepAngle, 360 - sweepAngle, false, mPaint); 171 | linePaint(); 172 | //画第一根线 173 | canvas.drawLine(startX1, startY1, stopX1, stopY1, mPaint); 174 | if (isAnimationOverHalf) { 175 | //-2 +2 是为了两根线尽可能靠拢 176 | canvas.drawLine(stopX1 - 2, stopY1 + 2, stopX2, stopY2, mPaint); 177 | } 178 | } 179 | } 180 | //对勾动画 181 | private class TickAnimation extends Animation { 182 | 183 | @Override 184 | protected void applyTransformation(final float interpolatedTime, Transformation t) { 185 | super.applyTransformation(interpolatedTime, t); 186 | if (interpolatedTime <= 0.5f) { 187 | stopX1 = startX1 + mRadius / 3 * interpolatedTime * 2; 188 | stopY1 = startY1 + mRadius / 3 * interpolatedTime * 2; 189 | isAnimationOverHalf = false; 190 | } else { 191 | stopX2 = stopX1 + (mRadius - 20) * (interpolatedTime - 0.5f) * 2; 192 | stopY2 = stopY1 - (mRadius - 20) * (interpolatedTime - 0.5f) * 2; 193 | isAnimationOverHalf = true; 194 | } 195 | invalidate(); 196 | } 197 | } 198 | 199 | @Override 200 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 201 | super.onSizeChanged(w, h, oldw, oldh); 202 | mResize = (w < h) ? w : h; 203 | setBound(); 204 | } 205 | 206 | private void setBound() { 207 | int paddingLeft = getPaddingLeft(); 208 | int paddingTop = getPaddingTop(); 209 | arcRectF.set(paddingLeft + mBorderWidth, paddingTop + mBorderWidth, mResize - paddingLeft - mBorderWidth, mResize - paddingTop - mBorderWidth); 210 | } 211 | 212 | public void startAnimation() { 213 | isNeedTick = false; 214 | if (animatorSet != null && animatorSet.isRunning()) { 215 | animatorSet.cancel(); 216 | } 217 | if (animatorSet == null) { 218 | animatorSet = new AnimatorSet(); 219 | } 220 | AnimatorSet set = loopAnimator(); 221 | animatorSet.play(set); 222 | animatorSet.addListener(new AnimatorListener() { 223 | 224 | private boolean isCancel = false; 225 | 226 | @Override 227 | public void onAnimationStart(Animator animation) { 228 | 229 | } 230 | 231 | @Override 232 | public void onAnimationRepeat(Animator animation) { 233 | } 234 | 235 | @Override 236 | public void onAnimationEnd(Animator animation) { 237 | if (!isCancel) { 238 | startAnimation(); 239 | } 240 | } 241 | 242 | @Override 243 | public void onAnimationCancel(Animator animation) { 244 | isCancel = true; 245 | } 246 | }); 247 | animatorSet.start(); 248 | } 249 | 250 | /** 251 | * 进度条旋转的动画 252 | */ 253 | private AnimatorSet loopAnimator() { 254 | //从小圈到大圈 255 | ValueAnimator holdAnimator1 = ValueAnimator.ofFloat(incrementAngele + DEFAULT_MIN_ANGLE, incrementAngele + 115f); 256 | holdAnimator1.addUpdateListener(new AnimatorUpdateListener() { 257 | @Override 258 | public void onAnimationUpdate(ValueAnimator animation) { 259 | incrementAngele = (float) animation.getAnimatedValue(); 260 | } 261 | }); 262 | holdAnimator1.setDuration(DEFAULT_DURATION); 263 | holdAnimator1.setInterpolator(new LinearInterpolator()); 264 | ValueAnimator expandAnimator = ValueAnimator.ofFloat(DEFAULT_MIN_ANGLE, DEFAULT_MAX_ANGLE); 265 | expandAnimator.addUpdateListener(new AnimatorUpdateListener() { 266 | @Override 267 | public void onAnimationUpdate(ValueAnimator animation) { 268 | sweepAngle = (float) animation.getAnimatedValue(); 269 | incrementAngele -= sweepAngle; 270 | invalidate(); 271 | } 272 | }); 273 | expandAnimator.setDuration(DEFAULT_DURATION); 274 | expandAnimator.setInterpolator(new DecelerateInterpolator(2)); 275 | //从大圈到小圈 276 | ValueAnimator holdAnimator = ValueAnimator.ofFloat(startAngle, startAngle + 115f); 277 | holdAnimator.addUpdateListener(new AnimatorUpdateListener() { 278 | @Override 279 | public void onAnimationUpdate(ValueAnimator animation) { 280 | startAngle = (float) animation.getAnimatedValue(); 281 | } 282 | }); 283 | 284 | holdAnimator.setDuration(DEFAULT_DURATION); 285 | holdAnimator.setInterpolator(new LinearInterpolator()); 286 | ValueAnimator narrowAnimator = ValueAnimator.ofFloat(DEFAULT_MAX_ANGLE, DEFAULT_MIN_ANGLE); 287 | narrowAnimator.addUpdateListener(new AnimatorUpdateListener() { 288 | @Override 289 | public void onAnimationUpdate(ValueAnimator animation) { 290 | sweepAngle = (float) animation.getAnimatedValue(); 291 | invalidate(); 292 | } 293 | }); 294 | 295 | narrowAnimator.setDuration(DEFAULT_DURATION); 296 | narrowAnimator.setInterpolator(new DecelerateInterpolator(2)); 297 | 298 | AnimatorSet set = new AnimatorSet(); 299 | set.play(holdAnimator1).with(expandAnimator); 300 | set.play(holdAnimator).with(narrowAnimator).after(holdAnimator1); 301 | return set; 302 | } 303 | //清除动画 304 | private void cancelAnimator() { 305 | if (animatorSet != null) { 306 | animatorSet.cancel(); 307 | isNeedTick = true; 308 | } 309 | } 310 | 311 | public void setSuccessfullyStatus() { 312 | if (animatorSet != null) { 313 | animatorSet.cancel(); 314 | isNeedTick = true; 315 | startAnimation(mTickAnimation); 316 | } 317 | } 318 | 319 | @Override 320 | public void setVisibility(int visibility) { 321 | switch (visibility) { 322 | case View.VISIBLE: 323 | startAnimation(); 324 | break; 325 | case View.INVISIBLE: 326 | cancelAnimator(); 327 | break; 328 | case View.GONE: 329 | cancelAnimator(); 330 | break; 331 | default: 332 | break; 333 | } 334 | super.setVisibility(visibility); 335 | } 336 | 337 | public void setBorderWidth(int width) { 338 | this.mBorderWidth = width; 339 | } 340 | 341 | public void setArcColor(int color) { 342 | this.arcColor = color; 343 | } 344 | 345 | public interface OnPasswordCorrectlyListener { 346 | void onPasswordCorrectly(); 347 | } 348 | 349 | } -------------------------------------------------------------------------------- /TestMockAlipay/alipay/src/main/java/com/mock/alipay/view/PasswordKeyboard.java: -------------------------------------------------------------------------------- 1 | package com.mock.alipay.view; 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.Handler; 8 | import android.os.Message; 9 | import android.util.AttributeSet; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.WindowManager; 14 | import android.widget.Button; 15 | import android.widget.GridLayout; 16 | 17 | 18 | import com.mock.alipay.R; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Random; 23 | 24 | /** 25 | * Created by chenjiawei on 16/10/28. 26 | */ 27 | 28 | public class PasswordKeyboard extends GridLayout implements View.OnClickListener, View.OnTouchListener { 29 | 30 | public static final String DEL = "删除"; 31 | 32 | public static final String DONE = "OK"; 33 | //因为UED是给的是iPhone设计稿,所以是按照等比的思想设置键盘Key的高度和宽度 34 | private static final int IPHONE = 779; 35 | //每个键盘Key的宽度,为屏幕宽度的三分之一 36 | private int keyWidth = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth() / 3; 37 | //每个键盘Key的高度 38 | private int keyHeight = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getHeight() * 59 / IPHONE; 39 | 40 | private int screenWidth = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth(); 41 | 42 | private Paint mPaint; 43 | //List集合存储Key,方便每次输错都能再次随机数字键盘 44 | private final List