實現Android支付寶聲波支付時的波紋檢視
阿新 • • 發佈:2018-12-31
我正在參加部落格之星,點選這裡投我一票吧,謝謝~
注意,這裡引入了xmlns:ripple,也就是自定義RippleLayout屬性生成的R的包路徑.
前言
自從支付寶聲波支付的波紋效果出來以後,這種形式就慢慢流行開來,比如各種安全軟體在掃描時會採用這種動畫效果,這種波紋盪漾起來也是增加了動感十足呢,如圖1。
圖1
今天我們就來學習如何實現這種波紋效果,以及最大限度的支援低版本的系統。
波紋實現
看到這種效果,最直接的感官就是波紋檢視慢慢的變大、並且顏色變淡,因此我在第一次摸索的過程中直接繼承自View,然後開啟一個執行緒來計算這個檢視的此時的大小以及顏色值,效果可以出來,但是有點卡。後面搜尋了一些資料,發現有更好的方式可以實現。
新的方式就是使用屬性動畫,但是屬性動畫在api 11及其以上才支援,因此這裡我們使用了NineOldAnimations動畫庫。基本原理就是自定義一個佈局,在這個佈局中會新增幾個背景檢視,也就是上述效果中的圓形檢視,然後使用者再指定一個自己的檢視,如上如中的支付按鈕。當用戶點選支付按鈕時,啟動動畫。此時,幾個背景檢視就會執行一個屬性動畫集,這些背景檢視的x, y軸都會放大,同時檢視的alpha屬性會慢慢的變小。這樣就產生了檢視變大、顏色慢慢淡化的效果,如圖2所示。
圖 2
程式碼實現 :
/* * The MIT License (MIT) * * Copyright (c) 2014-2015 [email protected] * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.simple.ripple; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.RelativeLayout; import com.nineoldandroids.animation.Animator; import com.nineoldandroids.animation.AnimatorSet; import com.nineoldandroids.animation.ObjectAnimator; import org.simple.ripplelayout.R; import java.util.ArrayList; /** * 這是一個類似支付寶聲波支付的波紋效果佈局,該佈局中預設添加了不可見的圓形的檢視,啟動動畫時會啟動縮放、顏色漸變動畫使得產生波紋效果. * 這些動畫都是無限迴圈的,並且每個View的動畫之間都有時間間隔,這些時間間隔就會導致檢視有大有小,從而產生波紋的效果. * * @author mrsimple */ public class RippleLayout extends RelativeLayout { /** * static final fields */ private static final int DEFAULT_RIPPLE_COUNT = 6; private static final int DEFAULT_DURATION_TIME = 3000; private static final float DEFAULT_SCALE = 4.0f; private static final int DEFAULT_RIPPLE_COLOR = Color.rgb(0x33, 0x99, 0xcc); private static final int DEFAULT_STROKE_WIDTH = 0; private static final int DEFAULT_RADIUS = 60; /** * */ private int mRippleColor = DEFAULT_RIPPLE_COLOR; private float mStrokeWidth = DEFAULT_STROKE_WIDTH; private float mRippleRadius = DEFAULT_RADIUS; private int mAnimDuration; private int mRippleViewNums; private int mAnimDelay; private float mRippleScale; private boolean animationRunning = false; /** * */ private Paint mPaint = new Paint(); /** * 動畫集,執行縮放、alpha動畫,使得背景色漸變 */ private AnimatorSet mAnimatorSet = new AnimatorSet(); /** * 動畫列表,儲存幾個動畫 */ private ArrayList<Animator> mAnimatorList = new ArrayList<Animator>(); /** * RippleView Params */ private LayoutParams mRippleViewParams; /** * @param context */ public RippleLayout(Context context) { super(context); init(context, null); } public RippleLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public RippleLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(final Context context, final AttributeSet attrs) { if (isInEditMode()) { return; } if (null != attrs) { initTypedArray(context, attrs); } initPaint(); initRippleViewLayoutParams(); generateRippleViews(); } private void initTypedArray(Context context, AttributeSet attrs) { final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleLayout); // mRippleColor = typedArray.getColor(R.styleable.RippleLayout_color, DEFAULT_RIPPLE_COLOR); mStrokeWidth = typedArray.getDimension(R.styleable.RippleLayout_strokeWidth, DEFAULT_STROKE_WIDTH); mRippleRadius = typedArray.getDimension(R.styleable.RippleLayout_radius, DEFAULT_RADIUS); mAnimDuration = typedArray.getInt(R.styleable.RippleLayout_duration, DEFAULT_DURATION_TIME); mRippleViewNums = typedArray.getInt(R.styleable.RippleLayout_rippleNums, DEFAULT_RIPPLE_COUNT); mRippleScale = typedArray.getFloat(R.styleable.RippleLayout_scale, DEFAULT_SCALE); // oh, baby, don't forget recycle the typedArray !! typedArray.recycle(); } private void initPaint() { mPaint = new Paint(); mPaint.setAntiAlias(true); mStrokeWidth = 0; mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mRippleColor); } private void initRippleViewLayoutParams() { // ripple view的大小為 半徑 + 筆寬的兩倍 int rippleSide = (int) (2 * (mRippleRadius + mStrokeWidth)); mRippleViewParams = new LayoutParams(rippleSide, rippleSide); // 居中顯示 mRippleViewParams.addRule(CENTER_IN_PARENT, TRUE); } /** * 計算每個RippleView之間的動畫時間間隔,從而產生波紋效果 */ private void calculateAnimDelay() { mAnimDelay = mAnimDuration / mRippleViewNums; } /** * 初始化RippleViews,並且將動畫設定到RippleView上,使之在x, y不斷擴大,並且背景色逐漸淡化 */ private void generateRippleViews() { calculateAnimDelay(); initAnimSet(); // 新增RippleView for (int i = 0; i < mRippleViewNums; i++) { RippleView rippleView = new RippleView(getContext()); addView(rippleView, mRippleViewParams); // 新增動畫 addAnimToRippleView(rippleView, i); } // x, y, alpha動畫一塊執行 mAnimatorSet.playTogether(mAnimatorList); } private void initAnimSet() { mAnimatorSet.setDuration(mAnimDuration); mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); } /** * 為每個RippleView新增動畫效果,並且設定動畫延時,每個檢視啟動動畫的時間不同,就會產生波紋 * * @param rippleView * @param i 檢視所在的索引 */ private void addAnimToRippleView(RippleView rippleView, int i) { // x軸的縮放動畫 final ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(rippleView, "scaleX", 1.0f, mRippleScale); scaleXAnimator.setRepeatCount(ObjectAnimator.INFINITE); scaleXAnimator.setRepeatMode(ObjectAnimator.RESTART); scaleXAnimator.setStartDelay(i * mAnimDelay); scaleXAnimator.setDuration(mAnimDuration); mAnimatorList.add(scaleXAnimator); // y軸的縮放動畫 final ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(rippleView, "scaleY", 1.0f, mRippleScale); scaleYAnimator.setRepeatMode(ObjectAnimator.RESTART); scaleYAnimator.setRepeatCount(ObjectAnimator.INFINITE); scaleYAnimator.setStartDelay(i * mAnimDelay); scaleYAnimator.setDuration(mAnimDuration); mAnimatorList.add(scaleYAnimator); // 顏色的alpha漸變動畫 final ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(rippleView, "alpha", 1.0f, 0f); alphaAnimator.setRepeatMode(ObjectAnimator.RESTART); alphaAnimator.setRepeatCount(ObjectAnimator.INFINITE); alphaAnimator.setDuration(mAnimDuration); alphaAnimator.setStartDelay(i * mAnimDelay); mAnimatorList.add(alphaAnimator); } public void startRippleAnimation() { if (!isRippleAnimationRunning()) { makeRippleViewsVisible(); mAnimatorSet.start(); animationRunning = true; } } private void makeRippleViewsVisible() { int childCount = this.getChildCount(); for (int i = 0; i < childCount; i++) { View childView = this.getChildAt(i); if (childView instanceof RippleView) { childView.setVisibility(VISIBLE); } } } public void stopRippleAnimation() { if (isRippleAnimationRunning()) { mAnimatorSet.end(); animationRunning = false; } } public boolean isRippleAnimationRunning() { return animationRunning; } /** * RippleView產生波紋效果, 預設不可見,當啟動動畫時才設定為可見 * * @author mrsimple */ private class RippleView extends View { public RippleView(Context context) { super(context); this.setVisibility(View.INVISIBLE); } @Override protected void onDraw(Canvas canvas) { int radius = (Math.min(getWidth(), getHeight())) / 2; canvas.drawCircle(radius, radius, radius - mStrokeWidth, mPaint); } } }
自定義屬性 attrs.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="RippleLayout"> <attr name="color" format="color" /> <attr name="strokeWidth" format="dimension" /> <attr name="radius" format="dimension" /> <attr name="duration" format="integer" /> <attr name="rippleNums" format="integer" /> <attr name="scale" format="float" /> </declare-styleable> </resources>
NineOldAnimations動畫庫
使用示例
從github clone一份或者將上述程式碼和attrs.xml拷貝到你的工程中,在佈局檔案中新增如下:
<org.simple.ripple.RippleLayout
xmlns:ripple="http://schemas.android.com/apk/org.simple.ripplelayout"
android:id="@+id/ripple_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
ripple:duration="3000"
ripple:radius="32dp"
ripple:rippleNums="1"
ripple:scale="4"
ripple:color="#8899CC" >
<ImageView
android:id="@+id/centerImage"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_centerInParent="true"
android:contentDescription="@string/app_name"
android:src="@drawable/phone2" />
</org.simple.ripple.RippleLayout>
注意,這裡引入了xmlns:ripple,也就是自定義RippleLayout屬性生成的R的包路徑.
程式碼中啟動動畫:
ImageView imageview;
RippleLayout layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout = (RippleLayout) findViewById(R.id.ripple_layout);
imageview = (ImageView) findViewById(R.id.centerImage);
imageview.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (layout.isRippleAnimationRunning()) {
layout.stopRippleAnimation();
} else {
layout.startRippleAnimation();
}
}
});
}
效果圖
github地址
我正在參加部落格之星,點選這裡投我一票吧,謝謝~