1. 程式人生 > >實現Android支付寶聲波支付時的波紋檢視

實現Android支付寶聲波支付時的波紋檢視

我正在參加部落格之星,點選這裡投我一票吧,謝謝~   

前言

自從支付寶聲波支付的波紋效果出來以後,這種形式就慢慢流行開來,比如各種安全軟體在掃描時會採用這種動畫效果,這種波紋盪漾起來也是增加了動感十足呢,如圖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地址

猛擊此進入

我正在參加部落格之星,點選這裡投我一票吧,謝謝~