Android 自定义View一个控件搞定多种水波纹涟漪扩散效果

效果图

这里写图片描述

实现思路

这个效果实现起来并不难,重要的是思路
此View满足了多种水波纹涟漪扩散效果,这要求它能满足很多的变化
根据上面的样式,可以看出此View需要满足以下变化

  • 圆圈从中心可循环向外扩散

  • 圆圈之间的扩散间距可以改变

  • 可控制扩散圆的渐变度

  • 圆圈可以是线条样式或者实心样式

  • 圆圈扩散的速度可以控制

  • 适配圆圈不同大小下的扩散效果

具体实现

创建自定义属性

首先为View创建自定义的xml属性
在工程的values目录下新建attrs.xml文件

    <declare-styleable name="mRippleView">
        <attr name="cColor" format="color"/>
        <attr name="cSpeed" format="integer"/>
        <attr name="cDensity" format="integer"/>
        <attr name="cIsFill" format="boolean"/>
        <attr name="cIsAlpha" format="boolean"/>
    </declare-styleable>

各个属性的作用如下

  • cColor:View控件的颜色
  • cSpeed:向外扩散的速度
  • cDensity:圆形波纹扩散的间距
  • cIsFill:是否开启填充模式,true为实心圆
  • cIsAlpha:是否开启渐变效果,true为开启

创建自定义View控件

新建RippleView类继承View类,重写它的三个构造方法,获取用户设置的属性,同时指定默认值

    public RippleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 获取用户配置属性
        TypedArray tya = context.obtainStyledAttributes(attrs, R.styleable.mRippleView);
        mColor = tya.getColor(R.styleable.mRippleView_cColor, Color.BLUE);
        mSpeed = tya.getInt(R.styleable.mRippleView_cSpeed, 1);
        mDensity = tya.getInt(R.styleable.mRippleView_cDensity, 10);
        mIsFill = tya.getBoolean(R.styleable.mRippleView_cIsFill, false);
        mIsAlpha = tya.getBoolean(R.styleable.mRippleView_cIsAlpha, false);
        tya.recycle();

        init();
    }

使用TypedArray读取完自定义的属性后一定要记得调用recycle方法释放掉

重写onMeasure

测量onMeasure,首先需要测量出View的宽和高,并指定View在wrap_content时的最小范围,对于View绘制流程还不熟悉的同学,可以先去了解下具体的绘制流程

http://blog.csdn.net/zhuwentao2150/article/details/53494760

重写onMeasure方法,其中我们要考虑当View的宽高被指定为wrap_content时的情况,如果我们不对wrap_content的情况进行处理,那么当使用者指定View的宽高为wrap_content时将无法正常显示出View

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int myWidthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int myWidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int myHeightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int myHeightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        // 获取宽
        if (myWidthSpecMode == MeasureSpec.EXACTLY) {
            // match_parent/精确值
            mWidth = myWidthSpecSize;
        } else {
            // wrap_content
            mWidth = DensityUtil.dip2px(mContext, 120);
        }

        // 获取高
        if (myHeightSpecMode == MeasureSpec.EXACTLY) {
            // match_parent/精确值
            mHeight = myHeightSpecSize;
        } else {
            // wrap_content
            mHeight = DensityUtil.dip2px(mContext, 120);
        }

        // 设置该view的宽高
        setMeasuredDimension(mWidth, mHeight);
    }

MeasureSpec的状态分为三种EXACTLY、AT_MOST、UNSPECIFIED,这里只要单独指定非精确值EXACTLY之外的情况就好了
本文中使用到的DensityUtil类,是为了将dp转换为px来使用,以便适配不同的屏幕显示效果

    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

重写onDraw

设计的整体思路如下图所示
这里写图片描述
先要实现圆形向外扩散的效果

  • 初始化第一个圆

这里的动画效果本来是想使用ValueAnimator属性动画的数值发生器来实现,但是我们这里有很多的计算需求,所以最后还是选择使用算法来实现,方便控制圆的一些参数
想要实现扩散的效果,这里思路是在每次更新View时动态改变圆的半径,同时还需要给圆设置渐变度数,所以决定用一个类来保存圆的状态,所有圆都存在一个List

// 添加第一个圆圈
mRipples = new ArrayList<>();
Circle c = new Circle(0, 255);
mRipples.add(c);

传入Circle类里的两个参数,第一个0表示圆的初始宽度,第二个255表示初始透明度

  • 添加新圆

要想实现不断有圆向外扩散,就需要在第一个圆扩散到一定范围时在圆心处再添加一个圆,这个的范围可以由圆的半径来控制,当List集合中最后一个圆的半径增加到某个值mDensity时,新的圆就从圆心处创建出来

// 添加圆
if (mRipples.size() > 0) {
    // 控制第二个圆出来的间距
    if (mRipples.get(mRipples.size() - 1).width > DensityUtil.dip2px(mContext, mDensity)) {
        mRipples.add(new Circle(0, 255));
    }
}
  • 删除List中多余的圆

List中的圆存储的数量不宜过多,多了内存消耗大,需要在当圆的半径超过View的宽度时就删掉这个圆

// 当圆超出View的宽度后删除
if (c.width > mWidth / 2) {
    mRipples.remove(i);
}

我们也可以在外切正方形的顶点处删除这个圆,需要用到勾股定律来计算扩散圆到外切正方形顶点的位置
这里写图片描述
如上图所示,得出计算公式为

// 使用勾股定律求得一个外切正方形中心点离顶点的距离
sqrtNumber = (int) (Math.sqrt(mWidth * mWidth + mHeight * mHeight) / 2);

这样就需要修改删除圆的位置了

if (c.width > sprtNumber) {
    mRipples.remove(i);
}
  • 控制扩散圆的渐变度

当圆在向View的边缘扩散时,渐变度数的改变需要动态来计算,渐变的计算算法要适配不同的圆宽度大小,我们知道透明度是0~255之间的,0表示完全透明,255表示百分百不透明,计算的时候就是需要将这个数值等份分配到圆的宽度里
这里要区分一点,对于圆来说,宽度是由圆心从0开始向外递增,而渐变度数则是由圆心从255开始向外递减,当圆与最外围的正方形内切时渐变度必须变为0,由此分析得知,公式如下

透明度 = 255 - 圆的宽度 * (255 / View宽度)

double alpha = 255 - c.width * (255 / ((double) mWidth / 2));
c.alpha = (int) alpha;

GitHub地址

https://github.com/zhuwentao2150/RippleView

总结

关于自定义View的总结部分在我的其它博客中已经写过蛮多了,有兴趣的可以去看看

Android 自定义View之八等份仪表盘:http://blog.csdn.net/zhuwentao2150/article/details/77588469
Android 自定义View之仿华为圆形加载进度条:http://blog.csdn.net/zhuwentao2150/article/details/77511148

  • 做自定义View,思路很重要,当想到一种方法可以实现时,先不要着急的做出来,试着换一个角度再多思考一下还有没有更好的实现方式
  • 本例子还缺少一些控制逻辑代码,将在之后添加上去并更新在GitHub中
  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
自定义一个水波进度 View,你需要完成以下几个步骤: 1. 创建一个自定义 View 类,并在构造函数中初始化一些必要的属性,如颜色、线宽等。 2. 重写 onSizeChanged() 方法,在该方法中获取 View 的宽度和高度,并计算出进度条的半径、圆心等相关参数。 3. 重写 onDraw() 方法,在该方法中绘制水波纹效果。 4. 在自定义 View 中添加一个 setProgress() 方法,用于设置进度条的进度。 5. 在布局文件中引入自定义 View,设置 layout_width 和 layout_height 属性,并在代码中调用 setProgress() 方法设置进度条的进度。 下面是一个简单的自定义水波进度 View 的代码示例: ```java public class WaterWaveProgressView extends View { private Paint mPaint; private int mWidth, mHeight; private float mRadius; private float mProgress; public WaterWaveProgressView(Context context) { super(context); init(); } public WaterWaveProgressView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public WaterWaveProgressView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.BLUE); mPaint.setStrokeWidth(5); mPaint.setStyle(Paint.Style.STROKE); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; mRadius = Math.min(mWidth, mHeight) / 2 * 0.8f; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius, mPaint); float angle = mProgress / 100 * 360; canvas.drawArc(mWidth / 2 - mRadius, mHeight / 2 - mRadius, mWidth / 2 + mRadius, mHeight / 2 + mRadius, -90, angle, false, mPaint); } public void setProgress(float progress) { mProgress = progress; invalidate(); } } ``` 通过调用 setProgress() 方法来更新进度条的进度,如下所示: ```java WaterWaveProgressView progressView = findViewById(R.id.progress_view); progressView.setProgress(50); // 设置进度为 50% ``` 在布局文件中引入自定义 View: ```xml <com.example.waterwaveprogressview.WaterWaveProgressView android:id="@+id/progress_view" android:layout_width="150dp" android:layout_height="150dp" /> ``` 这样就能够实现一个简单的水波进度 View 了。如果需要更加复杂的效果,可以在 onDraw() 方法中绘制多个水波纹,或者使用 Path 绘制波形等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值