• 三角函数之美-水波纹载入LoadingView


    一、前言

           学习是要总结的。近期几天学习了画图相关的,可是使用的机会较少,如今又快要遗忘了,这次看了水波纹的绘制。认为十分有意思,还是 把实现的方法记录下来。技术无他,为手熟尔。还是要多练习,空淡误国,实干兴邦,让我们看看今天的三角函数之美吧。

    二、概述

           肯定大家对中学学习的三角函数都不陌生吧。只是学习的sin、cos是超越函数一类函数。是初等函数的一种,借用维基百科的一张图:

           这里写图片描写叙述
            一个完整的正弦函数应该是这种:>y=Asin(ωx+φ)+h,A决定峰值,ω决定周期。φ表示初相位,h表示y轴的位置。在Android我们使用通常是Math.sin(30*Math.PI/180)这种形式,我原来搞不懂这样写的含义。后来才明确Math.PI就是π。π即180度。所以上述式子相当于sin(π/6)=1/2,在中学我们通常是sin(30°)=1/2,把sin(x)中的x赋值为角度,而在计算机语言x通常是弧度。

    这里写图片描写叙述
    这个流量界面就是通过sin函数曲线不断改变x位置绘制而来,实现的方式肯定也还有其他的方法,通过绘制贝塞尔曲线也有实现的案例。我们将通过样例逐渐对水波纹loadingview绘制,好了不多说了,手把手带你一步一步实现美丽炫酷的loading水波纹效果。

    三、波浪控件的实现

    首先我们先实现波浪控件的绘制:
    先计算坐标点:

     //这里我们以view的总宽度为周期,y = a * sin(2π) + b
            for (int i = 0; i < mTotalWidth; i++){
                mPointY[i] = (float) (20 * (Math.sin( 2 * Math.PI * i / w)));
            }

    绘制曲线

    for (int i = 0;i < mTotalWidth; i++){
                canvas.drawLine(i,mTotalHeight - mDaymicPointY[i] - 300,i,mTotalHeight,mPaint);
            }

    这里减去300仅仅是为了控制在Y轴上的位置,我们能够动态设置一个数值。在view上下移动。从而达到流量界面。我们先一步一步来。


    控制移动

     //改变两条波纹的移动点
            mXoffset += X_SPEED;
            //假设已经移动到末尾处,则到头又一次移动
            if(mXoffset > mTotalWidth){
                mXoffset = 0;
            }
     // 超出屏幕的挪到前面,mXoffset表示第一条水波纹要移动的距离
            int yIntelrval = mPointY.length - mXoffset;
            //使用System.arraycopy方式又一次填充第一条波纹的数据
            System.arraycopy(mPointY, 0, mDaymicPointY, mXoffset, yIntelrval);
            System.arraycopy(mPointY, yIntelrval, mDaymicPointY, 0, mXoffset);

    如今你会发现一个简单的水波纹效果出来了,例如以下图所看到的:
    这里写图片描写叙述
    你会发现这个水波纹跟我们的效果还是有些差距。别急,我们还有这个类PorterDuffXfermode,能够实现与所绘制的图像按一定规则进行混合,形成新的像素值,那么我们就能够先画一个水波纹效果,然后在画一个圆,使用以下这幅神图进行混合,你会发现奇迹的,PorterDuffXfermode的相关使用能够參考这边博客:

    Android中Canvas画图之PorterDuffXfermode使用及工作原理具体解释

    这里写图片描写叙述
    使用哪种规则呢,自己找找呗,都是进行某种规则进行混合的,我找到了,使用的是SrcIn,就能够进行混合实现效果了
    如今就是这种效果,我们在加一个seekbar。拖动显示效果。基本上就能够出现这个波浪loading效果了

            int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
                canvas.drawCircle(mTotalWidth / 2, mTotalHeight / 2, mTotalWidth / 2, mCriclePaint);
                //设置颜色混合模式
                mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
                //高减去宽除以2使水波纹底部在圆底部。动态改变percent值,在Y轴上变化
                for (int i = 0; i < mTotalWidth; i++) {
                    canvas.drawLine(i, mTotalHeight - mDaymicPointY[i] - (mTotalHeight - mTotalWidth) / 2 - percent * mTotalWidth / 100, i, mTotalHeight - (mTotalHeight - mTotalWidth) / 2, mPaint);
                }
                //最后将画笔去除Xfermode
                mPaint.setXfermode(null);
            canvas.restoreToCount(layerId);

    效果例如以下图所看到的
    这里写图片描写叙述

    咦,说好的明明是水波纹绿颜色呢,怎么变得浅了很多,硬件加速会对效果有影响的, Android从3.0(API Level 11)開始。在绘制View的时候支持硬件加速,充分利用GPU的特性,使得绘制更加平滑。可是会多消耗一些内存。

    提供了4个方面打开或者关闭硬件加速,这里也提一下:
    1.Application级别:

    <applicationandroid:hardwareAccelerated="true" ...>

    2.Activity级别:

    <activity android:hardwareAccelerated="false" ...>

    3.Window级别:

        getWindow().setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

    注:Android还不支持在Window级别关闭硬件加速。
    4.View级别:

    myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);

    注:Android还不支持在View级别开启硬件加速。
    我们直接在application以下全局关闭硬件加速就可以,以下的效果就是这种:

    这里写图片描写叙述
    我贴下所有实现过程的代码:

    public class WaveView extends View {
        private Paint mPaint, mCriclePaint,mTextPaint;
        // 倾斜或旋转、高速变化,当在屏幕上画一条直线时, 横竖不会出现锯齿,
        // 可是当斜着画时, 就会出现锯齿的效果,所以须要设置抗锯齿
        private DrawFilter mDrawFilter;
        private int mTotalHeight, mTotalWidth;
        private int mXoffset = 0;
        private float[] mPointY;
        private float[] mDaymicPointY;
        //波浪线移动速度
        private static final int X_SPEED = 20;
        private int percent;
    
        public void setPercent(int percent) {
            this.percent = percent;
        }
    
        public WaveView(Context context) {
            super(context);
            init();
        }
    
        public WaveView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            //图片线条(通用)的抗锯齿须要另外设置
            mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
            //实例化一个画笔
            mPaint = new Paint();
            //去除画笔锯齿
            mPaint.setAntiAlias(true);
            //设置画笔风格为实线
            mPaint.setStyle(Paint.Style.FILL);
            //设置画笔颜色
            mPaint.setColor(Color.GREEN);
            //实例化圆的画笔
            mCriclePaint = new Paint(mPaint);
            mCriclePaint.setColor(Color.parseColor("#88dddddd"));
            mCriclePaint.setAlpha(255);
            //实例化文字画笔
            mTextPaint = new Paint();
            mTextPaint.setAntiAlias(true);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //去除锯齿
            canvas.setDrawFilter(mDrawFilter);
            runWave();
            int canvasWidth = canvas.getWidth();
            int canvasHeight = canvas.getHeight();
            int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
                canvas.drawCircle(mTotalWidth / 2, mTotalHeight / 2, mTotalWidth / 2, mCriclePaint);
                //设置颜色混合模式
                mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
                //高减去宽除以2使水波纹底部在圆底部,动态改变percent值,在Y轴上变化
                for (int i = 0; i < mTotalWidth; i++) {
                    canvas.drawLine(i, mTotalHeight - mDaymicPointY[i] - (mTotalHeight - mTotalWidth) / 2 - percent * mTotalWidth / 100, i, mTotalHeight - (mTotalHeight - mTotalWidth) / 2, mPaint);
                }
                //最后将画笔去除Xfermode
                mPaint.setXfermode(null);
            canvas.restoreToCount(layerId);
            //改变两条波纹的移动点
            mXoffset += X_SPEED;
            //假设已经移动到末尾处。则到头又一次移动
            if (mXoffset > mTotalWidth) {
                mXoffset = 0;
            }
            String text = percent + "%";
            mTextPaint.setTextSize(80);
            float textLength = mTextPaint.measureText(text);
            canvas.drawText(text,(mTotalWidth - textLength) / 2,mTotalHeight / 2 - 20,mTextPaint);
            //引起view重绘
            postInvalidateDelayed(300);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mTotalHeight = h;
            mTotalWidth = w;
            //数组的长度为view的宽度
            mPointY = new float[w];
            mDaymicPointY = new float[w];
            //这里我们以view的总宽度为周期,y = a * sin(2π) + b
            for (int i = 0; i < mTotalWidth; i++) {
                mPointY[i] = (float) (20 * (Math.sin(2 * Math.PI * i / w)));
            }
        }
    
        private void runWave() {
            // 超出屏幕的挪到前面。mXoffset表示第一条水波纹要移动的距离
            int yIntelrval = mPointY.length - mXoffset;
            //使用System.arraycopy方式又一次填充第一条波纹的数据
            System.arraycopy(mPointY, 0, mDaymicPointY, mXoffset, yIntelrval);
            System.arraycopy(mPointY, yIntelrval, mDaymicPointY, 0, mXoffset);
        }
    }

    上面就是实现的所有代码了,都有凝视。相信大家能看懂,事实上实现这些效果还是非常easy的,可是我们还有很多扩展的地方,做成一个网络载入的等待动画,或者做成一个下载的的进度,等待大家自己去实现。

    源代码:下载地址猛戳

  • 相关阅读:
    6月16日
    9月15日
    9月14日
    9月13日
    9月12日
    6月11日
    梦断代码阅读笔记
    11周总结
    梦断代码阅读笔记
    10第一阶段意见评论
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/7199211.html
Copyright © 2020-2023  润新知