仿支付宝里面饿了么H5的红包特效

年底了,公司需求给APP搞个红包.要特效像支付宝里那个订餐进去的红包差不多. 我进去一看,那个是饿了么的H5实现的动画.有点像Android里的阻尼动画效果.看了一下Google的开源 ,和facebook的开源,发现低版本的还不行……这就尴尬了.为了兼容下面,只能放弃阻尼动画了.

既然是动画,那就可以用基础的来实现嘛.基础的平移 /放大/缩小.翻转.这些实现不了贝塞尔曲线那么狠的动画,但是仔细看看,很荣幸,还是可以模拟阻尼动画的,哈哈.阻尼动画——>用基础动画实现[由小放大(同时由透明到显示)—->接着由大到小]就可以实现了.那么红包在Activity顶层显示 ,那么我们就可以用一个PoPupWindow来放这个红包 .下来就可以开始我们的实现之路了.

PoPupWindow封装

    采用链试调用,更方便,快捷.
package com.townwang.popupwindowdemo;

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupWindow;

/**
 *  @author Town
 *  @created at 2018/1/11 14:25
 *  @Last Modified by: Town
 *  @Last Modified time: 2018/1/11 14:25
 *  @Remarks 自定义Popupwindow
 */

public class PWindow {
    private Context mContext;
    private int mWidth;
    private int mHeight;
    private boolean mIsFocusable = true;
    private boolean mIsOutside = true;
    private int mResLayoutId = -1;
    private View mContentView;
    private PopupWindow mPopupWindow;
    private int mAnimationStyle = -1;

    private boolean mClippEnable = true;//default is true
    private boolean mIgnoreCheekPress = false;
    private int mInputMode = -1;
    private PopupWindow.OnDismissListener mOnDismissListener;
    private int mSoftInputMode = -1;
    private boolean mTouchable = true;//default is ture
    private View.OnTouchListener mOnTouchListener;
    public PWindow(Context context){
        mContext = context;
    }

    public int getWidth() {
        return mWidth;
    }

    public int getHeight() {
        return mHeight;
    }

    /**
     *
     * @param anchor
     * @param xOff
     * @param yOff
     * @return
     */
    public PWindow showAsDropDown(View anchor, int xOff, int yOff){
        if(mPopupWindow!=null){
            mPopupWindow.showAsDropDown(anchor,xOff,yOff);
        }
        return this;
    }



    public PWindow showAsDropDown(View anchor){
        if(mPopupWindow!=null){
            mPopupWindow.showAsDropDown(anchor);
        }
        return this;
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public PWindow showAsDropDown(View anchor, int xOff, int yOff, int gravity){
        if(mPopupWindow!=null){
            mPopupWindow.showAsDropDown(anchor,xOff,yOff,gravity);
        }
        return this;
    }


    /**
     * 相对于父控件的位置(通过设置Gravity.CENTER,下方Gravity.BOTTOM等 ),可以设置具体位置坐标
     * @param parent
     * @param gravity
     * @param x the popup's x location offset
     * @param y the popup's y location offset
     * @return
     */
    public PWindow showAtLocation(View parent, int gravity, int x, int y){
        if(mPopupWindow!=null){
            mPopupWindow.showAtLocation(parent,gravity,x,y);
        }
        return this;
    }

    /**
     * 添加一些属性设置
     * @param popupWindow
     */
    private void apply(PopupWindow popupWindow){
        popupWindow.setClippingEnabled(mClippEnable);
        if(mIgnoreCheekPress){
            popupWindow.setIgnoreCheekPress();
        }
        if(mInputMode!=-1){
            popupWindow.setInputMethodMode(mInputMode);
        }
        if(mSoftInputMode!=-1){
            popupWindow.setSoftInputMode(mSoftInputMode);
        }
        if(mOnDismissListener!=null){
            popupWindow.setOnDismissListener(mOnDismissListener);
        }
        if(mOnTouchListener!=null){
            popupWindow.setTouchInterceptor(mOnTouchListener);
        }
        popupWindow.setTouchable(mTouchable);



    }

    private PopupWindow build(){

        if(mContentView == null){
            mContentView = LayoutInflater.from(mContext).inflate(mResLayoutId,null);
        }

        if(mWidth != 0 && mHeight!=0 ){
            mPopupWindow = new PopupWindow(mContentView,mWidth,mHeight);
        }else{
            mPopupWindow = new PopupWindow(mContentView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,true);
        }
        if(mAnimationStyle!=-1){
            mPopupWindow.setAnimationStyle(mAnimationStyle);
        }

        apply(mPopupWindow);//设置一些属性

        mPopupWindow.setFocusable(mIsFocusable);
        mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        mPopupWindow.setOutsideTouchable(mIsOutside);

        if(mWidth == 0 || mHeight == 0){
            mPopupWindow.getContentView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
            //如果外面没有设置宽高的情况下,计算宽高并赋值
            mWidth = mPopupWindow.getContentView().getMeasuredWidth();
            mHeight = mPopupWindow.getContentView().getMeasuredHeight();
        }

        mPopupWindow.update();

        return mPopupWindow;
    }

    /**
     * 关闭popWindow
     */
    public void dissmiss(){
        if(mPopupWindow!=null){
            mPopupWindow.dismiss();
        }
    }


    public static class PopupWindowBuilder{
        private PWindow mCustomPopWindow;

        public PopupWindowBuilder(Context context){
            mCustomPopWindow = new PWindow(context);
        }
        public PopupWindowBuilder size(int width,int height){
            mCustomPopWindow.mWidth = width;
            mCustomPopWindow.mHeight = height;
            return this;
        }


        public PopupWindowBuilder setFocusable(boolean focusable){
            mCustomPopWindow.mIsFocusable = focusable;
            return this;
        }



        public PopupWindowBuilder setView(int resLayoutId){
            mCustomPopWindow.mResLayoutId = resLayoutId;
            mCustomPopWindow.mContentView = null;
            return this;
        }

        public PopupWindowBuilder setView(View view){
            mCustomPopWindow.mContentView = view;
            mCustomPopWindow.mResLayoutId = -1;
            return this;
        }

        public PopupWindowBuilder setOutsideTouchable(boolean outsideTouchable){
            mCustomPopWindow.mIsOutside = outsideTouchable;
            return this;
        }

        /**
         * 设置弹窗动画
         * @param animationStyle
         * @return
         */
        public PopupWindowBuilder setAnimationStyle(int animationStyle){
            mCustomPopWindow.mAnimationStyle = animationStyle;
            return this;
        }


        public PopupWindowBuilder setClippingEnable(boolean enable){
            mCustomPopWindow.mClippEnable =enable;
            return this;
        }


        public PopupWindowBuilder setIgnoreCheekPress(boolean ignoreCheekPress){
            mCustomPopWindow.mIgnoreCheekPress = ignoreCheekPress;
            return this;
        }

        public PopupWindowBuilder setInputMethodMode(int mode){
            mCustomPopWindow.mInputMode = mode;
            return this;
        }

        public PopupWindowBuilder setOnDissmissListener(PopupWindow.OnDismissListener onDissmissListener){
            mCustomPopWindow.mOnDismissListener = onDissmissListener;
            return this;
        }


        public PopupWindowBuilder setSoftInputMode(int softInputMode){
            mCustomPopWindow.mSoftInputMode = softInputMode;
            return this;
        }


        public PopupWindowBuilder setTouchable(boolean touchable){
            mCustomPopWindow.mTouchable = touchable;
            return this;
        }

        public PopupWindowBuilder setTouchIntercepter(View.OnTouchListener touchIntercepter){
            mCustomPopWindow.mOnTouchListener = touchIntercepter;
            return this;
        }


        public PWindow create(){
            //构建PopWindow
            mCustomPopWindow.build();
            return mCustomPopWindow;
        }

    }

}

这就是封装后的类了.值得注意的是:如果

new PopupWindow(mContentView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,true);

是这个属性.那么你设置

    mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        mPopupWindow.setOutsideTouchable(mIsOutside);

都不起作用! 很尴尬.我这里红包不让点击外面取消,所以才设置ViewGroup.LayoutParams.MATCH_PARENT.

红包承载写完了.接下来是动画

模仿阻尼动画效果

  1. style
    <style name="CustomPopWindowStyle">
        <item name="android:windowEnterAnimation">@anim/popwindow_anim_in</item>
        <item name="android:windowExitAnimation">@anim/popwindow_anim_out</item>
    </style>

没错,你没看错.我们采用视图进入和退出动画来改动完成

接下来是进入动画

  1. 进入动画@anim/popwindow_anim_in
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:duration="100"
        />
    <scale
    android:interpolator= "@android:anim/decelerate_interpolator"
    android:duration="100"
    android:fromXScale="1.0"
    android:fromYScale="1.0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="1"
    android:repeatMode="reverse"
    android:startOffset="0"
    android:toXScale="1.5"
    android:toYScale="1.5" />
</set>

说明:

interpolator指定动画插入器,常见的有加速减速插入器accelerate_decelerate_interpolator,加速插入器elerate_interpolator,减速插入器decelerate_interpolator。

fromXScale,fromYScale,动画开始前X,Y的缩放,0.0为不显示,1.0为正常大小

toXScale,toYScale,动画最终缩放的倍数,1.0为正常大小,大于1.0放大

pivotX,pivotY动画起始位置,相对于屏幕的百分比,两个都为50%表示动画从屏幕中间开始

startOffset,动画多次执行的间隔时间,如果只执行一次,执行前会暂停这段时间,单位毫秒 duration,一次动画效果消耗的时间,单位毫秒,值越小动画速度越快

repeatCount,动画重复的计数,动画将会执行该值+1次

repeatMode,动画重复的模式,reverse为反向,当第偶次执行时,动画方向会相反。restart为重新执行,方向不变

3.退出动画@anim/popwindow_anim_out

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:duration="100"
        />
    <scale
        android:interpolator= "@android:anim/decelerate_interpolator"
        android:duration="100"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="1"
        android:repeatMode="reverse"
        android:startOffset="0"
        android:toXScale="2.0"
        android:toYScale="2.0" />
</set>

接下来看我们链式调用大法

    @Override
    public void onClick(View view) {
        View contentView = LayoutInflater.from(this).inflate(R.layout.meny_window, null);
        View view1 = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
        new PWindow.PopupWindowBuilder(this)
                .setAnimationStyle(R.style.CustomPopWindowStyle)
                .setView(contentView).setFocusable(true)
                .setOutsideTouchable(true)
                .create()
                .showAsDropDown(view1);
    }

至此..完成 项目地址 : 点击下载