1. 程式人生 > >Android 水波紋特效

Android 水波紋特效

先看一下動畫效果:第一次上傳gif圖片 先錄的mp4視訊 後來轉的gif
這裡寫圖片描述
Android動畫在這裡就不詳細講了,網上一大把,不清楚的可以去看看。這裡只講這個動畫的視線邏輯。
這個動畫這裡分四個部分:
1.最底部圖片心跳動畫
2.水波紋擴散動畫
3.頭像出現的時機動畫
4.中間個人頭像動畫

在這裡先講第一部分:最底部圖片心跳動畫

 int size = mList.length;
        animPoints = new Animation[size];
        /**
         * 五個小點 動畫顯示
         * */
        for (int i = 0
; i < size; i++) { final int j = i; LayoutParams mSmallCircleParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); if (j == 0) { mSmallCircleParams.rightMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_140); mSmallCircleParams.gravity
= Gravity.RIGHT; mSmallCircleParams.topMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_380); } else if (j == 1) { mSmallCircleParams.rightMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_75); mSmallCircleParams.gravity
= Gravity.RIGHT; mSmallCircleParams.topMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_89); } else if (j == 2) { mSmallCircleParams.leftMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_17); mSmallCircleParams.topMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_150); } else if (j == 3) { mSmallCircleParams.leftMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_39); mSmallCircleParams.topMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_240); } else if (j == 4) { mSmallCircleParams.rightMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_112); mSmallCircleParams.gravity = Gravity.RIGHT; mSmallCircleParams.topMargin = ResUtil.getDimens(mContext, R.dimen.common_padding_224); } ImageView mSmallCircleView = new ImageView(getContext()); mSmallCircleView.setImageResource(mList[j]); addView(mSmallCircleView, mSmallCircleParams); /**載入動畫*/ animPoints[j] = AnimationUtils.loadAnimation(getContext(), R.anim.anim_the_heartbeat); mSmallCircleView.startAnimation(animPoints[j]); animPoints[j].setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { if (mAnimationProgressListener != null) { mAnimationProgressListener.startAnimation(); } } @Override public void onAnimationEnd(Animation animation) { if (mAnimationProgressListener != null) { mAnimationProgressListener.endAnimation(); } if (null != animPoints && null != animPoints[j]) { animPoints[j].cancel(); } } @Override public void onAnimationRepeat(Animation animation) { } }); }

在這裡先講第二部分:水波紋擴散動畫

 //佈局 管理器,讓圓劇中顯示
        LayoutParams rippleParams = new LayoutParams((int) (2 * (rippleRadius + rippleStrokeWidth * 3)), (int) (2 * (rippleRadius + rippleStrokeWidth * 3)));
        rippleParams.gravity = Gravity.CENTER;

        //動畫的集合
        ArrayList<Animator> animatorList = new ArrayList<>();

        //水波紋 縮放、漸變動畫
        for (int i = 0; i < rippleAmount; i++) {

            RipplView rippleView = new RipplView(getContext());
            addView(rippleView, rippleParams);
            rippleViewList.add(rippleView);

            //伸縮動畫
            float rippleScale = 6.0f;
            final ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(rippleView, "ScaleX", 1.0f, rippleScale);
            scaleXAnimator.setRepeatCount(ValueAnimator.INFINITE);
            scaleXAnimator.setRepeatMode(ObjectAnimator.RESTART);
            scaleXAnimator.setStartDelay(i * rippleDelay);
            animatorList.add(scaleXAnimator);

            final ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(rippleView, "ScaleY", 1.0f, rippleScale);
            scaleYAnimator.setRepeatCount(ValueAnimator.INFINITE);
            scaleYAnimator.setRepeatMode(ObjectAnimator.RESTART);
            scaleYAnimator.setStartDelay(i * rippleDelay);
            animatorList.add(scaleYAnimator);

            //透明度動畫
            final ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(rippleView, "Alpha", 1.0f, 0f);
            alphaAnimator.setRepeatCount(ValueAnimator.INFINITE);
            alphaAnimator.setRepeatMode(ObjectAnimator.RESTART);
            alphaAnimator.setStartDelay(i * rippleDelay);
            animatorList.add(alphaAnimator);
        }

        //開始動畫
        animatorSet.playTogether(animatorList);
        //動畫的監聽
        animatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                if (mAnimationProgressListener != null) {
                    mAnimationProgressListener.startAnimation();
                }
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                if (mAnimationProgressListener != null) {
                    mAnimationProgressListener.endAnimation();
                }
            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });

在這裡講第三部分:頭像出現的時機動畫
簡單的理解就是劃分了六個區域:

private final static int showNum = 5;
    private static long delayMillis = 4000;

    private int radiusP;
    private int radiusC;
    private int radiusB;
    private int pointCX;
    private int pointCY;
    private int page = 0;
    private int lastNum = 0;
    private boolean isAddShowing = false;
    private List<String> mGodHeadPhoto = new ArrayList<>();
    private Handler mHandler = new Handler();
    private UserRunnable userRunnable;
    private CircleImageView[] imageViews;
    private ObjectAnimator[] animator;

    /**
     * 設定大神頭像
     */
    public void setUserPhoto(List<String> godHeadPhoto) {
        Log.e(TAG, "setUserPhoto==11==" + (null == godHeadPhoto ? 0 : godHeadPhoto.size()));
        if (null == godHeadPhoto || godHeadPhoto.isEmpty()) {
            return;
        }
        mGodHeadPhoto.addAll(godHeadPhoto);
        Log.e(TAG, "setUserPhoto==22==" + mGodHeadPhoto.size() + "==isAddShowing==" + isAddShowing);
        if (isAddShowing) {
            return;
        }
        page = 0;
        lastNum = 0;
        isAddShowing = true;
        if (radiusP <= 0 || radiusC <= 0 || radiusB <= 0 || pointCX <= 0 || pointCY <= 0) {
            this.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    RippleView.this.getViewTreeObserver().removeOnPreDrawListener(this);
                    //RippleView寬
                    int rippleW = RippleView.this.getWidth();
                    //RippleView高
                    int rippleH = RippleView.this.getHeight();
                    radiusP = ResUtil.getDimens(mContext, R.dimen.common_padding_16);
                    radiusC = ResUtil.getDimens(mContext, R.dimen.common_padding_36);
                    int padding = ResUtil.getDimens(mContext, R.dimen.common_padding_20);
                    radiusB = rippleW / 2 - radiusP - padding;
                    pointCX = rippleW / 2;
                    pointCY = rippleH / 2;

                    startShowUserList(true);
                    return true;
                }
            });
        } else {
            startShowUserList(true);
        }
    }

    /**
     * 開始顯示
     *
     * @param isFirst 是否第一次顯示
     */
    private void startShowUserList(boolean isFirst) {
        if (isHidden || (!isFirst && isAddShowing)) {
            return;
        }
        isAddShowing = true;
        showNextUserList();
    }

    /**
     * 獲得當前顯示大神list
     *
     * @return
     */
    private List<String> getCurShowList() {
        if (null == mGodHeadPhoto || mGodHeadPhoto.isEmpty()) {
            return null;
        }
        int size = mGodHeadPhoto.size();
        if (showNum >= size) {
            page = 0;
            lastNum = size;
            return mGodHeadPhoto;
        }
        int start = lastNum + page * showNum;
        if (start >= size) {
            return null;
        }
        int end = lastNum + (page + 1) * showNum;
        if (end >= size) {
            //列表已經展示結束
            page = 0;
            if (end == size) {
                lastNum = 0;
                return mGodHeadPhoto.subList(start, size);
            } else {
                lastNum = end - size;
                List<String> list = new ArrayList<>();
                list.addAll(mGodHeadPhoto.subList(start, size));
                list.addAll(mGodHeadPhoto.subList(0, lastNum));
                return list;
            }
        } else {
            page++;
            return mGodHeadPhoto.subList(start, end);
        }
    }

    /**
     * @param radiusP 五個隨機大神頭像半徑
     * @param radiusC 中心點圓半徑
     * @param radiusB 顯示區域半徑
     * @param pointCX 中心點x座標
     * @param pointCY 中心點y座標
     */
    private void addViewPhoto(int radiusP, int radiusC, int radiusB, int pointCX, int pointCY, List<String> list) {
        Log.e(TAG, "addViewPhoto==" + (null == list ? 0 : list.size()) + "==lastNum==" + lastNum + "==page==" + page + "==mGodHeadPhoto.size==" + mGodHeadPhoto.size());
        if (null != imageViews) {
            for (int i = 0; i < imageViews.length; i++) {
                if (null != imageViews[i]) {
                    RippleView.this.removeView(imageViews[i]);
                }
            }
        }
        if (userRunnable != null) {
            mHandler.removeCallbacks(userRunnable);
        }
        int size = null == list ? 0 : list.size();
        if (size == 0) {
            isAddShowing = false;
            animPhotos = null;
            imageViews = null;
            return;
        }
        animPhotos = new Animation[size];
        imageViews = new CircleImageView[size];
        animator = new ObjectAnimator[size];

        //動畫控制元件的寬高
        for (int i = 0; i < size; i++) {
            final int k = i;
            int x1;
            int y1;
            int x2;
            int y2;
            if (k == 0) {
                /**隨機按鈕分四片區域顯示
                 * 左上角區域
                 */
                x1 = pointCX - radiusB;
                y1 = pointCY - radiusB;

                x2 = pointCX - radiusP;
                y2 = pointCY - radiusC - radiusP;
            } else if (k == 1) {
                /**隨機按鈕分四片區域顯示
                 * 右上角區域
                 */
                x1 = pointCX + radiusP;
                y1 = pointCY - radiusB;

                x2 = pointCX + radiusB;
                y2 = pointCY - radiusC;
            } else if (k == 2) {
                /**隨機按鈕分四片區域顯示
                 * 左下角區域
                 */
                x1 = pointCX - radiusB;
                y1 = pointCY + radiusC + radiusP;

                x2 = pointCX - radiusP;
                y2 = pointCY + radiusB;
            } else if (k == 3) {
                /**隨機按鈕分四片區域顯示
                 * 右下角區域
                 */
                x1 = pointCX + radiusP;
                y1 = pointCY + radiusC + radiusP;

                x2 = pointCX + radiusB;
                y2 = pointCY + radiusB;
            } else {
                int yshu = getRandomInt(Integer.MAX_VALUE) % 2;
                if (yshu == 0) {
                    /**隨機按鈕分四片區域顯示
                     * 左中區域
                     */
                    x1 = pointCX - radiusB;
                    y1 = pointCY - radiusC + radiusP;

                    x2 = pointCX - radiusC - radiusP;
                    y2 = pointCY + radiusC - radiusP;
                } else {
                    /**隨機按鈕分四片區域顯示 j
                     * 右中區域
                     */
                    x1 = pointCX + radiusC + radiusP;
                    y1 = pointCY - radiusC + radiusP;

                    x2 = pointCX + radiusB;
                    y2 = pointCY + radiusC - radiusP;
                }
            }
//            Log.e("addView", "addView x ==== == " + ((x2 - x1) / 2 + x1));
//            Log.e("addView", "addView y ==== == " + ((y2 - y1) / 2 + y1));
            //隨機生成一個屏內的位置來顯示動畫

            int xs = 0;
            int ys = 0;
            boolean flag = true;
            while (flag) {
                xs = getRandomInt(Integer.MAX_VALUE) % (x2 - x1) + x1;
                ys = getRandomInt(Integer.MAX_VALUE) % (y2 - y1) + y1;
                if (isCricle(xs, ys)) {
                    flag = false;
                }
            }

            final int x = xs;
            final int y = ys;
            final String pathPhoto = list.get(k);

//            Log.e("addView", "addView x   === " + x);
//            Log.e("addView", "addView y   === " + y);
            mHandler.postDelayed(new WaitPhotoRunnable(pathPhoto, k, x, y), k * 180);

        }
        showNextUserList();
    }

deom下載地址