自定义View之圆形水波扩散动效

 

这个效果做出来以后,真的美极了!放在你的应用中,无疑增添了光彩!

效果图

效果1 效果2 效果3

其实,第一种效果,才是产品的需求要的效果。第三种效果,是不是很熟悉?支付宝的咻一咻!哈哈,无意中,我就写出来了。

实现步骤

1.attrs.xml定义属性

 <declare-styleable name="WaveView">
        <!--圆颜色-->
        <attr name="wave_color" format="color"/>
        <!--中心圆图片半径-->
        <attr name="wave_coreImageRadius" format="integer"/>
        <!--波浪圆之间间距,值越小越窄-->
        <attr name="wave_width" format="integer"/>
    </declare-styleable>

2.WaveView的初始化

  /**
     * 波浪圆圈颜色
     */
    private int mColor = getResources().getColor(R.color.yellow);
    /**
     * 第一个圆圈的半径(也就是圆形图片的半径)
     */
    private int mImageRadius=50;
    /**
     * 波浪圆之间间距
     */
    private int mWidth = 3;
    /**
     * 最大宽度
     */
    private Integer mMaxRadius = 300;
    /**
     * 是否正在扩散中
     */
    private boolean mIsWave = false;
    // 透明度集合
    private List<Integer> mAlphas = new ArrayList<>();
    // 扩散圆半径集合
    private List<Integer> mRadius = new ArrayList<>();
    private Paint mPaint;

    public WaveView(Context context) {
        this(context, null);
    }

    public WaveView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WaveView, defStyleAttr, 0);
        mColor = a.getColor(R.styleable.WaveView_wave_color, mColor);
        mWidth = a.getInt(R.styleable.WaveView_wave_width, mWidth);
        mImageRadius = a.getInt(R.styleable.WaveView_wave_coreImageRadius, mImageRadius);
        a.recycle();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
//        mAlphas.add(255);
//        mRadius.add(0);

    }

3.重写onDraw()

    @Override
    public void onDraw(Canvas canvas) {
        // 绘制扩散圆
        mPaint.setColor(mColor);
        for (int i = 0; i < mAlphas.size(); i++) {
            // 设置透明度
            Integer alpha = mAlphas.get(i);
            mPaint.setAlpha(alpha);
            // 绘制波浪圆
            Integer radius = mRadius.get(i);
            canvas.drawCircle(getWidth() / 2, getHeight() / 2,  mImageRadius+radius, mPaint);

            if (alpha > 0 && mImageRadius+radius < mMaxRadius) {
                alpha = (int) (255.0F * (1.0F - (mImageRadius+radius) * 1.0f / mMaxRadius));
                mAlphas.set(i, alpha);
                mRadius.set(i, radius + 1);
            }else if(alpha < 0 && mImageRadius+radius > mMaxRadius){
              // 当最外面那个圆达到了View的宽度时,移除,保证内存的回收  
                mRadius.remove(i);
                mAlphas.remove(i);
            }

        }
        // 判断当波浪圆扩散到指定宽度时添加新扩散圆
//        if (mRadius.get(mRadius.size() - 1) == mWidth) {
//           addWave();
//        }
        if (mIsWave) {
            invalidate();
        }
    }

思路:一直不停的在根据list中的半径值和alpha值在画对应的圆,list中有多少个圆,就会画出多少个,当alpha的值小于0了,视觉上,人眼看不到了,或者已经到了View的边界,就将他移除。减少内存的占用。

4.提供的一些公共的方法,方便调用

/**
     * 开始扩散
     */
    public void start() {
        mIsWave = true;
        invalidate();
    }

    /**
     * 停止扩散
     */
    public void stop() {
        mIsWave = false;
    }

    /**
     * 是否扩散中
     */
    public boolean isWave() {
        return mIsWave;
    }

    /**
     * 设置波浪圆颜色
     */
    public void setColor(int colorId) {
        mColor = colorId;
    }

    /**
     * 设置波浪圆之间间距
     */
    public void setWidth(int width) {
        mWidth = width;
    }

    /**
     * 设置中心圆半径
     */
    public void setMaxRadius(int maxRadius) {
        mMaxRadius = maxRadius;
    }

    public void setImageRadius(int imageRadius) {
        mImageRadius = imageRadius;
    }

    public void addWave(){
        mAlphas.add(255);
        mRadius.add(0);
    }

5.Activity中的调用和xml布局

WaveActivity.java

    private ImageView head;
    private WaveView wave;
    private ScaleAnimation scaleAnimation;
    private MediaPlayer mPlayer;
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wave);
        scaleAnimation = new ScaleAnimation(1.2f, 1f, 1.2f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        scaleAnimation.setDuration(500);
        scaleAnimation.setFillAfter(true);
        wave = (WaveView) findViewById(R.id.wave);
        head = (ImageView) findViewById(R.id.head);
        mPlayer = MediaPlayer.create(this, R.raw.water_wave);
        head.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                wave.addWave();
                head.startAnimation(scaleAnimation);
                if(mPlayer.isPlaying()){
                    mPlayer.stop();
                    try {
                        mPlayer.prepare();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                mPlayer.start();
            }
        });
        wave.start();
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        wave.setImageRadius(head.getWidth()/2);
    }

activity_wave.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black"
    >
    <com.dx.demi.view.WaveView
        android:id="@+id/wave"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:wave_color="@color/yellow"
        app:wave_coreImageRadius="30"
        app:wave_width="40"/>

    <ImageView
        android:id="@+id/head"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/user"
        android:layout_centerInParent="true"/>
</RelativeLayout>

6.需要注意的细节

/**
     * 获取View的宽高在构造方法中拿不到的,getWidth(),getHeight()都会为零
     * @param hasWindowFocus
     */
    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        mMaxRadius = getWidth() > getHeight() ? getHeight() / 2 : getWidth() / 2;
        invalidate();
    }

    /**
     * 防止window是去焦点时,也就是应用在后台时,停止View的绘制
     */
    @Override
    public void invalidate() {
        if (hasWindowFocus()) {
            super.invalidate();
        }
    }

三种效果的切换

1.我目前贴的代码实现的是效果3(咻一咻):点击图片,图片会放大,同时会播放背景音乐(网上随便找了个还听得过去的),并增加一个新的波浪圆。

2.效果1:打开View的界面,每个一段固定的距离,就会产生一个新的波浪圆。波浪圆是空心的。这个只要设置画笔的风格为STROKE,并设置StrokeWidth。Activity中注释掉动画,注释掉点击事件,注释掉音乐播放。但是要记得一开始就得添加新圆,不然啥都没有。毕竟不像效果3那样,点击图片才有。

 mPaint.setStyle(Paint.Style.STROKE);
 mPaint.setStrokeWidth(5);
 mAlphas.add(255);
 mRadius.add(0);

3.效果2:打开View的界面,每个一段固定的距离,就会产生一个新的波浪圆。波浪圆是实心的。Activity中注释掉动画,注释掉点击事件,注释掉音乐播放。设置画笔的风格为FILL就OK了,默认风格就是FILL。

总结:

在完成这个自定义View的时候,花了很长时间。我也不是一开始就是这样的一个思路。没有想到过用集合来存放半径,没有第一时间想到getWidth()的值在构造方法中会获取不到.所以还是得多想,多尝试。我们才会进步,提高!在自定义View时,一定要明白一个真理:”onDraw()方法重新调用时,会抹去上一次绘制过的图像“。有些地方,我写的可能不是很好,还需要优化。欢迎点评!

源代码链接

https://github.com/Demidong/ClockView

原文地址:https://www.cnblogs.com/xgjblog/p/8343078.html