• Android案例手册 - 定位点圆形水波纹和椭圆水波纹


    往期文章分享

    本文约18千字,新手阅读需要18分钟,复习需要6分钟收藏随时查阅不再迷路

    👉关于作者

    众所周知,人生是一个漫长的流程,不断克服困难,不断反思前进的过程。在这个过程中会产生很多对于人生的质疑和思考,于是我决定将自己的思考,经验和故事全部分享出来,以此寻找共鸣 !!!
    专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)
    有什么需要欢迎私我,交流群让学习不再孤单

    在这里插入图片描述

    👉前提

    这是小空坚持写的Android新手向系列,欢迎品尝。

    大佬(√)

    新手(√√√)

    👉实践过程

    先看效果图

    定位点圆形水波纹和椭圆水波纹.gif

    在相关需求中需要定位医学听诊点,给予用户一个提示反馈。所以封装了一个View。

    分为圆形效果和椭圆形效果,并且Java版本和Kotlin版本都有。

    如图中,我们可以看出,水波纹一点点扩张且越远越淡,那么我们就知道怎么做了。

    1. 声明一个圆类,随着时间的推移这个圆的透明度和半径进行变化
    2. 声明一个圆类数组,然后隔一段时间创建一个圆类添加进数组
    3. 在onDraw中遍历数组绘制出来,判断当前时间距离创建时间大于多少秒就从圆类数组中删除即可。
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <!--人物背景图-->
        <ImageView
            android:layout_width="600dp"
            android:layout_height="900dp"
            android:src="@drawable/weizhi_bg" />
     
        <cn.appstudy.customView.WaveViewJava
            android:id="@+id/testWaveViewJava"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_marginStart="150dp"
            android:layout_marginTop="420dp" />
     
        <cn.appstudy.customView.WaveOvalViewJava
            android:id="@+id/testWaveOvalViewJava"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_marginStart="260dp"
            android:layout_marginTop="520dp" />
     
    </RelativeLayout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    然后代码中找到这个布局View的id,调用start方法即可。

    😜WaveView-圆形

    如下代码类

    创建一个可控变量定为波纹从创建到校时的持续时间,当然这个变量完全可以供外部修改。

    创建一个变量控制波纹创建的间隔。

    创建圆的相关变量(半径,颜色,画笔等等)。

    start()方法为启动方法,开启反复的Runnable,间隔时间就是波纹创建时间,里面写创建圆的逻辑。

    创建圆类,将当前时间记录下来保存进去,圆类中提供两个方法,更新的当前时间减去创建时候保存的的“当前时间”,这个插值是越来越大的,那么就可以修改透明度和半径。

    之后便是onDraw中遍历圆类的数组,判断当前的时间和圆类中保存的创建时间,大于设定持续时间的值则销毁,否则用画笔在canvas进行绘制。

    Java版

    /**
     * @author akitaka
     * @filename WaveViewJava
     */
    public class WaveViewJava extends View {
        private long mDuration = 2000; // 一个波纹从创建到消失的持续时间
        /**
         * 插值器属性,可理解为指定动画如何变化的动动;有多种
         * LinearInterpolator--画从开始到结束,变化率是线性变化
         */
        private Interpolator mInterpolator = new LinearInterpolator();
        private float mInitialRadius;   // 初始波纹半径
        private float mMaxRadius;   // 最大波纹半径
        private Paint mPaint;
        private List<Circle> mCircleList = new ArrayList<>();//当前正在显示的圆集合
        private boolean mIsRunning;//绘制线程是否开始
        private int mSpeed = 500;   // 波纹的创建速度,每500ms创建一个
        private long mLastCreateTime;   //上一个创建圆的时间
        private boolean mMaxRadiusSet;   //如果设置了最大半径
        private float mMaxRadiusRate = 0.85f;   // 如果没有设置mMaxRadius,可mMaxRadius = 最小长度 * mMaxRadiusRate;
     
        public WaveViewJava (Context context) {
            super(context);
        }
        public WaveViewJava (Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  //抗锯齿
            mPaint.setColor(Color.WHITE);
            setPaintStyle(Paint.Style.FILL);  //填充式
        }
        /**
         * 波纹半径率
         * @param maxRadiusRate
         */
        public void setMaxRadiusRate(float maxRadiusRate) {
            this.mMaxRadiusRate = maxRadiusRate;
        }
        /**
         * 画笔颜色
         * @param color
         */
        public void setColor(int color) {
            mPaint.setColor(color);
        }
        /**
         * 初始半径
         * @param radius
         */
        public void setInitialRadius(float radius) {
            mInitialRadius = radius;
        }
        /**
         * 设置波纹持续时间
         * @param duration
         */
        public void setDuration(long duration) {
            this.mDuration = duration;
        }
        /**
         * 最大波纹半径
         * @param maxRadius
         */
        public void setMaxRadius(float maxRadius) {
            this.mMaxRadius = maxRadius;
            mMaxRadiusSet = true;
        }
        /**
         * 波纹创建速度
         * @param speed
         */
        public void setSpeed(int speed) {
            mSpeed = speed;
        }
        /**
         * 插值器,达到不同的效果
         * @param interpolator
         */
        public void setInterpolator(Interpolator interpolator) {
            mInterpolator = interpolator;
            if (mInterpolator == null) {
                mInterpolator = new LinearInterpolator();
            }
        }
        /**
         * 提供给外界 设置画笔风格
         * @param style
         */
        public void setPaintStyle(Paint.Style style) {
            mPaint.setStyle(style);
        }
        /**
         * 提供给外界开来是整个过程
         */
        public void start() {
            if (!mIsRunning) {
                mIsRunning = true;
                mCreateCircle.run(); //开线程
            }
        }
        /**
         * 停止
         */
        public void stop() {
            mIsRunning = false;
        }
        private Runnable mCreateCircle = new Runnable() {
            @Override
            public void run() {
                if (mIsRunning) {
                    newCircle();
                    postDelayed(mCreateCircle, mSpeed); // 每隔mSpeed毫秒创建一个圆
                }
            }
        };
        /**
         * 创建圆
         */
        private void newCircle() {
            long currentTime = System.currentTimeMillis();
            if (currentTime - mLastCreateTime < mSpeed) {
                return;
            }
            Circle circle = new Circle();
            mCircleList.add(circle);
            invalidate();   //重新调用onDraw绘制
            mLastCreateTime = currentTime;
        }
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            if (!mMaxRadiusSet) {
                mMaxRadius = Math.min(w, h) * mMaxRadiusRate / 2.0f;
            }
        }
        @Override
        protected void onDraw(Canvas canvas) {
            Iterator<Circle> iterator = mCircleList.iterator();   //得带期遍历
            while (iterator.hasNext()) { //是否=还有元素
                Circle circle = iterator.next();//拿到下一个
                if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {
                    mPaint.setAlpha(circle.getAlpha());  //设置透明度
                    canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint);  //设置半径
                } else {  //如果创建的时间大于最大  则移除
                    iterator.remove();
                }
            }
            if (mCircleList.size() > 0) {
                postInvalidateDelayed(10);  //10毫秒刷新  快速给人错觉是动态的  其实是静态的很多圆
            }
        }
        /**
         * 绘制圆的类   getAlpha、getCurrentRadius插值器原因
         * 你可以在外部设置不同的插值器器,来实现波纹的不同动态效果;详情请学习插值器;
         */
        private class Circle {
            private long mCreateTime;  //当前添加圆时间
            public Circle() {
                this.mCreateTime = System.currentTimeMillis();
            }
            /**
             * 获得透明度  ↓0(消失)
             * getInterpolation传值越大,返回越大 因为是LinearInterpolator 传值返回一致
             * @return
             */
            public int getAlpha() {
                //当前时间处于总时间的百分比   当前时间/圆创建-消失总时间
                float percent = (float) ((System.currentTimeMillis() - mCreateTime) * 1.0 / mDuration);
                return (int) ((1.0f - mInterpolator.getInterpolation(percent)) * 255);
            }
            /**
             * 获得当前的半径
             */
            public float getCurrentRadius() {
                float percent = (float) ((System.currentTimeMillis() - mCreateTime) * 1.0 / mDuration);
                //最小半径+当前扩大百分比半径
                return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178

    Kotlin版

    /**
     * Created by akitaka on 2022-06-22.
     * @author akitaka
     * @filename WaveViewKotlin
     * @describe
     * @email 960576866@qq.com
     */
    class WaveViewKotlin : View {
        constructor(context: Context?) : this(context, null)
        constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
        var mDuration: Long = 2000 // 一个波纹从创建到消失的持续时间
        /**
         * 插值器属性,可理解为指定动画如何变化的动动;有多种
         * LinearInterpolator--画从开始到结束,变化率是线性变化
         */
        var mInterpolator: Interpolator = LinearInterpolator()
        var mInitialRadius = 0f// 初始波纹半径
        var mMaxRadius = 0f// 最大波纹半径
        var mPaint: Paint? = null
        var mCircleList: MutableList<Circle> = ArrayList() //当前正在显示的圆集合
        var mIsRunning = false//绘制线程是否开始
        var mSpeed = 500 // 波纹的创建速度,每500ms创建一个
        var mLastCreateTime: Long = 0//上一个创建圆的时间
        var mMaxRadiusSet = false//如果设置了最大半径
        var mMaxRadiusRate = 0.85f // 如果没有设置mMaxRadius,可mMaxRadius = 最小长度 * mMaxRadiusRate;
    
        init {
            mPaint = Paint(Paint.ANTI_ALIAS_FLAG);  //抗锯齿
            mPaint!!.color = Color.GREEN;
            setPaintStyle(Paint.Style.FILL);  //填充式
        }
        /**
         * 波纹半径率
         * @param maxRadiusRate
         */
        fun setMaxRadiusRate(maxRadiusRate: Float) {
            mMaxRadiusRate = maxRadiusRate
        }
        /**
         * 画笔颜色
         * @param color
         */
        fun setColor(color: Int) {
            mPaint!!.color = color
        }
        /**
         * 初始半径
         * @param radius
         */
        fun setInitialRadius(radius: Float) {
            mInitialRadius = radius
        }
        /**
         * 设置波纹持续时间
         * @param duration
         */
        fun setDuration(duration: Long) {
            mDuration = duration
        }
        /**
         * 最大波纹半径
         * @param maxRadius
         */
        fun setMaxRadius(maxRadius: Float) {
            mMaxRadius = maxRadius
            mMaxRadiusSet = true
        }
        /**
         * 波纹创建速度
         * @param speed
         */
        fun setSpeed(speed: Int) {
            mSpeed = speed
        }
        /**
         * 插值器,达到不同的效果
         * @param interpolator
         */
        fun setInterpolator(interpolator: Interpolator) {
            mInterpolator = interpolator
            if (mInterpolator == null) {
                mInterpolator = LinearInterpolator()
            }
        }
        /**
         * 提供给外界 设置画笔风格
         * @param style
         */
        fun setPaintStyle(style: Paint.Style?) {
            mPaint!!.style = style
        }
        /**
         * 提供给外界开来是整个过程
         */
        fun start() {
            if (!mIsRunning) {
                mIsRunning = true
                mCreateCircle.run() //开线程
            }
        }
        /**
         * 停止
         */
        fun stop() {
            mIsRunning = false
        }
        private val mCreateCircle: Runnable = object : Runnable {
            override fun run() {
                if (mIsRunning) {
                    newCircle()
                    postDelayed(this, mSpeed.toLong()) // 每隔mSpeed毫秒创建一个圆
                }
            }
        }
        /**
         * 创建圆
         */
        private fun newCircle() {
            val currentTime = System.currentTimeMillis()
            if (currentTime - mLastCreateTime < mSpeed) {
                return
            }
            val circle = Circle()
            mCircleList.add(circle)
            invalidate() //重新调用onDraw绘制
            mLastCreateTime = currentTime
        }
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            if (!mMaxRadiusSet) {
                mMaxRadius = Math.min(w, h) * mMaxRadiusRate / 2.0f
            }
        }
        override fun onDraw(canvas: Canvas) {
            val iterator = mCircleList.iterator() //得带期遍历
            while (iterator.hasNext()) { //是否=还有元素
                val circle = iterator.next() //拿到下一个
                if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {
                    mPaint!!.alpha = circle.getAlpha() //设置透明度
                    canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), circle.getCurrentRadius(), mPaint!!) //设置半径
                } else {  //如果创建的时间大于最大  则移除
                    iterator.remove()
                }
            }
            if (mCircleList.size > 0) {
                postInvalidateDelayed(10) //10毫秒刷新  快速给人错觉是动态的  其实是静态的很多圆
            }
        }
        /**
         * 绘制圆的类   getAlpha、getCurrentRadius插值器原因
         * 你可以在外部设置不同的插值器器,来实现波纹的不同动态效果;详情请学习插值器;
         * kotlin要想内部类访问外部类的成员变量 需要inner修饰
         */
        inner class Circle {
            val mCreateTime: Long //当前添加圆时间
    
            /**
             * 获得透明度  ↓0(消失)
             * getInterpolation传值越大,返回越大 因为是LinearInterpolator 传值返回一致
             *
             * @return
             */
            fun getAlpha(): Int {
                //当前时间处于总时间的百分比   当前时间/圆创建-消失总时间
                val percent: Float = ((System.currentTimeMillis() - mCreateTime) * 1.0 / mDuration).toFloat()
                return ((1.0f - mInterpolator.getInterpolation(percent)) * 255).toInt()
            }
    
            /**
             * 获得当前的半径
             */
            fun getCurrentRadius(): Float {
                val percent: Float = ((System.currentTimeMillis() - mCreateTime) * 1.0 / mDuration).toFloat()
                //最小半径+当前扩大百分比半径
                return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius)
            }
    
            init {
                mCreateTime = System.currentTimeMillis()
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182

    😜WaveOvalView-椭圆

    椭圆的绘制和圆的绘制仅仅差在onDraw中,其他的都一样。onDraw使用canvas.drawOval即可。

    Java版

    /**
     *
     * @author akitaka
     * @filename WaveOvalViewJava
     * @describe 椭圆形的水波纹,而且椭圆的扁平方向也需要控制;;后期可考虑封装,继承WaveViewJava或者整体
     * @email 960576866@qq.com
     */
    public class WaveOvalViewJava extends View {
        private long mDuration = 2000; // 一个波纹从创建到消失的持续时间
        /**
         * 插值器属性,可理解为指定动画如何变化的动动;有多种
         * LinearInterpolator--画从开始到结束,变化率是线性变化
         */
        private Interpolator mInterpolator = new LinearInterpolator();
        private float mInitialRadius;   // 初始波纹半径
        private float mMaxRadius;   // 最大波纹半径
        private Paint mPaint;
        private List<Circle> mCircleList = new ArrayList<>();//当前正在显示的圆集合
        private boolean mIsRunning;//绘制线程是否开始
        private int mSpeed = 500;   // 波纹的创建速度,每500ms创建一个
        private long mLastCreateTime;   //上一个创建圆的时间
        private boolean mMaxRadiusSet;   //如果设置了最大半径
        private float mMaxRadiusRate = 0.85f;   // 如果没有设置mMaxRadius,可mMaxRadius = 最小长度 * mMaxRadiusRate;
    
        public WaveOvalViewJava(Context context) {
            super(context);
        }
        public WaveOvalViewJava (Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  //抗锯齿
            setPaintStyle(Paint.Style.FILL);  //填充式
        }
        /**
         * 波纹半径率
         * @param maxRadiusRate
         */
        public void setMaxRadiusRate(float maxRadiusRate) {
            this.mMaxRadiusRate = maxRadiusRate;
        }
        /**
         * 画笔颜色
         * @param color
         */
        public void setColor(int color) {
            mPaint.setColor(color);
        }
        /**
         * 初始半径
         * @param radius
         */
        public void setInitialRadius(float radius) {
            mInitialRadius = radius;
        }
        /**
         * 设置波纹持续时间
         * @param duration
         */
        public void setDuration(long duration) {
            this.mDuration = duration;
        }
        /**
         * 最大波纹半径
         * @param maxRadius
         */
        public void setMaxRadius(float maxRadius) {
            this.mMaxRadius = maxRadius;
            mMaxRadiusSet = true;
        }
        /**
         * 波纹创建速度
         * @param speed
         */
        public void setSpeed(int speed) {
            mSpeed = speed;
        }
        /**
         * 插值器,达到不同的效果
         * @param interpolator
         */
        public void setInterpolator(Interpolator interpolator) {
            mInterpolator = interpolator;
            if (mInterpolator == null) {
                mInterpolator = new LinearInterpolator();
            }
        }
        /**
         * 提供给外界 设置画笔风格
         * @param style
         */
        public void setPaintStyle(Paint.Style style) {
            mPaint.setStyle(style);
        }
        /**
         * 提供给外界开来是整个过程
         */
        public void start() {
            if (!mIsRunning) {
                mIsRunning = true;
                mCreateCircle.run(); //开线程
            }
        }
        /**
         * 停止
         */
        public void stop() {
            mIsRunning = false;
        }
        private Runnable mCreateCircle = new Runnable() {
            @Override
            public void run() {
                if (mIsRunning) {
                    newCircle();
                    postDelayed(mCreateCircle, mSpeed); // 每隔mSpeed毫秒创建一个圆
                }
            }
        };
        /**
         * 创建圆
         */
        private void newCircle() {
            long currentTime = System.currentTimeMillis();
            if (currentTime - mLastCreateTime < mSpeed) {
                return;
            }
            Circle circle = new Circle();
            mCircleList.add(circle);
            invalidate();   //重新调用onDraw绘制
            mLastCreateTime = currentTime;
        }
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            if (!mMaxRadiusSet) {
                mMaxRadius = Math.min(w, h) * mMaxRadiusRate / 2.0f;
            }
        }
        @Override
        protected void onDraw(Canvas canvas) {
            Iterator<Circle> iterator = mCircleList.iterator();   //得带期遍历
            while (iterator.hasNext()) { //是否=还有元素
                Circle circle = iterator.next();//拿到下一个
                if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {
                    mPaint.setAlpha(circle.getAlpha());  //设置透明度
    //                canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint);  //设置半径来控制为椭圆
                    RectF ovalRect = new RectF();
                    //椭圆的绘制封装 需要更精细的考虑, left,top小于 right,bottom;;
                    ovalRect.left = getWidth() / 2 - circle.getCurrentRadius();
                    ovalRect.top = getWidth() / 2 - 1.5f * circle.getCurrentRadius();
                    ovalRect.right = getWidth() / 2 + circle.getCurrentRadius();
                    ovalRect.bottom = getWidth() / 2 + 1.5f * circle.getCurrentRadius();
                    canvas.drawOval(ovalRect, mPaint);
                    //需要API 21
    //                canvas.drawOval(circle.getCurrentRadius(),circle.getCurrentRadius()-10,circle.getCurrentRadius(),circle.getCurrentRadius()-10,mPaint);
                } else {  //如果创建的时间大于最大  则移除
                    iterator.remove();
                }
            }
            if (mCircleList.size() > 0) {
                postInvalidateDelayed(10);  //10毫秒刷新  快速给人错觉是动态的  其实是静态的很多圆
            }
        }
        /**
         * 绘制圆的类   getAlpha、getCurrentRadius插值器原因
         * 你可以在外部设置不同的插值器器,来实现波纹的不同动态效果;详情请学习插值器;
         */
        private class Circle {
            private long mCreateTime;  //当前添加圆时间
            public Circle() {
                this.mCreateTime = System.currentTimeMillis();
            }
            /**
             * 获得透明度  ↓0(消失)
             * getInterpolation传值越大,返回越大 因为是LinearInterpolator 传值返回一致
             * @return
             */
            public int getAlpha() {
                //当前时间处于总时间的百分比   当前时间/圆创建-消失总时间
                float percent = (float) ((System.currentTimeMillis() - mCreateTime) * 1.0 / mDuration);
                return (int) ((1.0f - mInterpolator.getInterpolation(percent)) * 255);
            }
            /**
             * 获得当前的半径
             */
            public float getCurrentRadius() {
                float percent = (float) ((System.currentTimeMillis() - mCreateTime) * 1.0 / mDuration);
                //最小半径+当前扩大百分比半径
                return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190

    Kotlin版

    /**
     * Created by akitaka on 2022-06-23.
     * @author akitaka
     * @filename WaveOvalViewKotlin
     * @describe
     * @email 960576866@qq.com
     */
    class WaveOvalViewKotlin : View {
        constructor(context: Context?) : this(context, null)
        constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
    
        var mDuration: Long = 2000 // 一个波纹从创建到消失的持续时间
        /**
         * 插值器属性,可理解为指定动画如何变化的动动;有多种
         * LinearInterpolator--画从开始到结束,变化率是线性变化
         */
        var mInterpolator: Interpolator = LinearInterpolator()
        var mInitialRadius = 0f// 初始波纹半径
        var mMaxRadius = 0f// 最大波纹半径
        var mPaint: Paint? = null
        var mCircleList: MutableList<Circle> = ArrayList() //当前正在显示的圆集合
        var mIsRunning = false//绘制线程是否开始
        var mSpeed = 500 // 波纹的创建速度,每500ms创建一个
        var mLastCreateTime: Long = 0//上一个创建圆的时间
        var mMaxRadiusSet = false//如果设置了最大半径
        var mMaxRadiusRate = 0.85f // 如果没有设置mMaxRadius,可mMaxRadius = 最小长度 * mMaxRadiusRate;
        init {
            mPaint = Paint(Paint.ANTI_ALIAS_FLAG);  //抗锯齿
            mPaint!!.color = Color.GREEN;
            setPaintStyle(Paint.Style.FILL);  //填充式
        }
        /**
         * 波纹半径率
         * @param maxRadiusRate
         */
        fun setMaxRadiusRate(maxRadiusRate: Float) {
            mMaxRadiusRate = maxRadiusRate
        }
        /**
         * 画笔颜色
         * @param color
         */
        fun setColor(color: Int) {
            mPaint!!.color = color
        }
        /**
         * 初始半径
         * @param radius
         */
        fun setInitialRadius(radius: Float) {
            mInitialRadius = radius
        }
        /**
         * 设置波纹持续时间
         * @param duration
         */
        fun setDuration(duration: Long) {
            mDuration = duration
        }
    
        /**
         * 最大波纹半径
         * @param maxRadius
         */
        fun setMaxRadius(maxRadius: Float) {
            mMaxRadius = maxRadius
            mMaxRadiusSet = true
        }
        /**
         * 波纹创建速度
         * @param speed
         */
        fun setSpeed(speed: Int) {
            mSpeed = speed
        }
        /**
         * 插值器,达到不同的效果
         * @param interpolator
         */
        fun setInterpolator(interpolator: Interpolator) {
            mInterpolator = interpolator
            if (mInterpolator == null) {
                mInterpolator = LinearInterpolator()
            }
        }
        /**
         * 提供给外界 设置画笔风格
         *
         * @param style
         */
        fun setPaintStyle(style: Paint.Style?) {
            mPaint!!.style = style
        }
        /**
         * 提供给外界开来是整个过程
         */
        fun start() {
            if (!mIsRunning) {
                mIsRunning = true
                mCreateCircle.run() //开线程
            }
        }
        /**
         * 停止
         */
        fun stop() {
            mIsRunning = false
        }
        private val mCreateCircle: Runnable = object : Runnable {
            override fun run() {
                if (mIsRunning) {
                    newCircle()
                    postDelayed(this, mSpeed.toLong()) // 每隔mSpeed毫秒创建一个圆
                }
            }
        }
        /**
         * 创建圆
         */
        private fun newCircle() {
            val currentTime = System.currentTimeMillis()
            if (currentTime - mLastCreateTime < mSpeed) {
                return
            }
            val circle = Circle()
            mCircleList.add(circle)
            invalidate() //重新调用onDraw绘制
            mLastCreateTime = currentTime
        }
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            if (!mMaxRadiusSet) {
                mMaxRadius = Math.min(w, h) * mMaxRadiusRate / 2.0f
            }
        }
        override fun onDraw(canvas: Canvas) {
            val iterator: MutableIterator<Circle> = mCircleList.iterator() //得遍历
            while (iterator.hasNext()) { //是否=还有元素
                val circle = iterator.next() //拿到下一个
                if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {
                    mPaint!!.alpha = circle.getAlpha() //设置透明度
                    //                canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint);  //设置半径来控制为椭圆
                    val ovalRect = RectF()
                    //椭圆的绘制封装 需要更精细的考虑, left,top小于 right,bottom;;
                    ovalRect.left = width / 2 - circle.getCurrentRadius()
                    ovalRect.top = width / 2 - 1.5f * circle.getCurrentRadius()
                    ovalRect.right = width / 2 + circle.getCurrentRadius()
                    ovalRect.bottom = width / 2 + 1.5f * circle.getCurrentRadius()
                    canvas.drawOval(ovalRect, mPaint)
                    //需要API 21
    //                canvas.drawOval(circle.getCurrentRadius(),circle.getCurrentRadius()-10,circle.getCurrentRadius(),circle.getCurrentRadius()-10,mPaint);
                } else {  //如果创建的时间大于最大  则移除
                    iterator.remove()
                }
            }
            if (mCircleList.size > 0) {
                postInvalidateDelayed(10) //10毫秒刷新  快速给人错觉是动态的  其实是静态的很多圆
            }
        }
    
        /**
         * 绘制圆的类   getAlpha、getCurrentRadius插值器原因
         * 你可以在外部设置不同的插值器器,来实现波纹的不同动态效果;详情请学习插值器;
         * kotlin要想内部类访问外部类的成员变量 需要inner修饰
         */
        inner class Circle {
            val mCreateTime: Long //当前添加圆时间
    
            /**
             * 获得透明度  ↓0(消失)
             * getInterpolation传值越大,返回越大 因为是LinearInterpolator 传值返回一致
             *
             * @return
             */
            fun getAlpha(): Int {
                //当前时间处于总时间的百分比   当前时间/圆创建-消失总时间
                val percent: Float = ((System.currentTimeMillis() - mCreateTime) * 1.0 / mDuration).toFloat()
                return ((1.0f - mInterpolator.getInterpolation(percent)) * 255).toInt()
            }
    
            /**
             * 获得当前的半径
             */
            fun getCurrentRadius(): Float {
                val percent: Float = ((System.currentTimeMillis() - mCreateTime) * 1.0 / mDuration).toFloat()
                //最小半径+当前扩大百分比半径
                return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius)
            }
    
            init {
                mCreateTime = System.currentTimeMillis()
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194

    👉其他

    📢作者:小空和小芝中的小空
    📢转载说明-务必注明来源:https://zhima.blog.csdn.net/
    📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。

    温馨提示点击下方卡片获取更多意想不到的资源。
    空名先生

  • 相关阅读:
    编写自动化软件+python
    详解nginx的root与alias
    仿CSGO盲盒开箱源码 盲盒商城源码 盲盒开箱源码 潮物盲盒商城源码
    SparkStreaming消费kafka存储到Elasticsearch
    将存在课题的过程可视化,丰田的“自工程完结”是什么?
    C#常识篇(二)
    在Docker中安装nacos
    【Y 码力】WAL 与性能
    通过机器视觉对硬盘容器上盖的字符进行视觉识别,判断是否混料
    【spring mvc】配置默认Servlet处理器
  • 原文地址:https://blog.csdn.net/qq_27489007/article/details/125460814