恩恩,整了一天多,再次整出一个loading框,看来我对loading框是情有独钟,好了,不多bb,先上图:

  

android Spinner自定义item android自定义loading_贝塞尔曲线

  恩,就是这么个东东,较之前两个,有了点技术含量,但是其实也不是很难,之所以做了一天多,原因是又特么踩了一个坑,坑了我一个下午的时间,伤不起,至于是什么坑,下面再说;

  好了,完成这个之前必要的知识储备,二阶贝塞尔曲线,也去网上看了一些文章,还有说要三阶贝塞尔曲线知识的,其实我觉得没必要,二阶就够了,下面附上一个链接,看完就知道贝塞尔曲线到底是个

什么 东东了:

  知道了贝塞尔曲线,下面就可以开始说道说道了。

  要完成这个loading图案,我认为难点主要有两个(不了解如何实现之前),一个是如何实现水波纹效果,一个就是如何将背景的圆和水波纹有效的组合起来(这里有坑,请注意)!

  首先是水波纹效果,恩,其实就是两组正选曲线一直做平移运动(之所以所难点,是因为不揭穿之前,我是一脸懵逼的-_-!!);

  然后组合的话就要用到Paint的setXfermode()接口,在里面指定一个PorterDufffermode类的枚举对象,具体有哪些,这里也附上一个链接:

  好了,具体是如何的,上代码来分析吧!

 

public class WaveLoading extends View {
    private Paint wavePaint;
    private Bitmap waveBitmap;
    private Canvas waveCanvas; //画水波纹相关的东东
    private Paint cirPaint;
    private Bitmap cirBitmap;
    private Canvas cirCanvas;  //画背景园相关的东东
    private Paint mPaint;      //canvas画布的画笔
    private Bitmap midBitmap;
    private Canvas midCanvas;
    private Paint midPaint;    //中间画布,用来组合背景圆和水波纹
    private int viewWidth;     //控件的宽
    private int viewHeight;    //控件的高
    private Path mPath;        // 画水波纹用到的路径
    private int offset;        //水波纹达到波动效果的偏移量
    private int progress = 0;   //进度条
    private android.os.Handler mHander = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            invalidate();
        }
    };

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

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

    public WaveLoading(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initSetting();
    }

    private void initSetting() {
        wavePaint = new Paint();
        wavePaint.setColor(Color.parseColor("#0404FF"));
        wavePaint.setAntiAlias(true);
        wavePaint.setStyle(Paint.Style.FILL);
        cirPaint = new Paint();
        cirPaint.setColor(Color.parseColor("#88888888"));
        cirPaint.setAntiAlias(true);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL);
        midPaint = new Paint();
        midPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        viewWidth = MeasureSpec.getSize(widthMeasureSpec);
        viewHeight = MeasureSpec.getSize(heightMeasureSpec);
        offset = -viewWidth;
        initBitMap();
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void initBitMap() {
        if (waveBitmap == null) {
            waveBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
            waveCanvas = new Canvas(waveBitmap);
        }
        if (cirBitmap == null) {
            cirBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
            cirCanvas = new Canvas(cirBitmap);
        }
        if (midBitmap == null) {
            midBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
            midCanvas = new Canvas(midBitmap);
        }
    }


    @Override
    protected void onDraw(Canvas canvas) {
        cirCanvas.drawCircle(viewWidth / 2, viewHeight / 2, 150, cirPaint);
        drawSrc();
        midCanvas.drawBitmap(cirBitmap, 0, 0, midPaint);
        midPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
        midCanvas.drawBitmap(waveBitmap, 0, 0, midPaint);
        String percent = progress <= 300 ? progress / 3 + "%" : "100%";
        mPaint.setTextSize(50);
        mPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(percent, (viewWidth) / 2, (viewHeight) / 2, mPaint);

        canvas.drawBitmap(midBitmap, 0, 0, mPaint);
        super.onDraw(canvas);
    }

    private void drawSrc() {
        waveBitmap.eraseColor(Color.parseColor("#00000000"));
        mPath = new Path();
        wavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
        for (int i = 0; i < 4; i++) {
            if (i % 2 == 0) {
                mPath.reset();
                mPath.moveTo(offset + i * viewWidth / 2, viewHeight - progress);
                mPath.quadTo(offset + (1 + 2 * i) * viewWidth / 4, viewHeight - progress - 40,
                        offset + (1 + i) * viewWidth / 2, viewHeight - progress);
                waveCanvas.drawPath(mPath, wavePaint);
            } else {
                mPath.reset();
                mPath.moveTo(offset + i * viewWidth / 2, viewHeight - progress);
                mPath.quadTo(offset + (1 + 2 * i) * viewWidth / 4, viewHeight - progress + 40,
                        offset + (1 + i) * viewWidth / 2, viewHeight - progress);
                waveCanvas.drawPath(mPath, wavePaint);
            }
        }
        mPath.reset();
        waveCanvas.drawRect(0, viewHeight - progress, viewWidth, viewHeight, wavePaint);
    }

    public void startLoading() {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                progress++;
                offset += 3;
                if (offset == 0) offset = -viewWidth;
                if (progress == viewHeight + 20) progress = 0;
                mHander.obtainMessage(1).sendToTarget();
            }
        };
        timer.schedule(task, 10, 10);
    }
}

onMeasure和那些初始化方法就不再分析,直接说重点:
1.水波纹代码的实现
private void drawSrc() {
        waveBitmap.eraseColor(Color.parseColor("#00000000"));
        mPath = new Path();
        wavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
        for (int i = 0; i < 4; i++) {
            if (i % 2 == 0) {
                mPath.reset();
                mPath.moveTo(offset + i * viewWidth / 2, viewHeight - progress);
                mPath.quadTo(offset + (1 + 2 * i) * viewWidth / 4, viewHeight - progress - 40,
                        offset + (1 + i) * viewWidth / 2, viewHeight - progress);
                waveCanvas.drawPath(mPath, wavePaint);
            } else {
                mPath.reset();
                mPath.moveTo(offset + i * viewWidth / 2, viewHeight - progress);
                mPath.quadTo(offset + (1 + 2 * i) * viewWidth / 4, viewHeight - progress + 40,
                        offset + (1 + i) * viewWidth / 2, viewHeight - progress);
                waveCanvas.drawPath(mPath, wavePaint);
            }
        }
        mPath.reset();
        waveCanvas.drawRect(0, viewHeight - progress, viewWidth, viewHeight, wavePaint);
    }
实现后的效果大致是这个样子的

  

android Spinner自定义item android自定义loading_ide_02

 可以看到,是个很明显的正弦波,拔高是通过改变progress,波动是通过改变offset实现,具体的代码在startLoading()方法中,这里使用了PorterDuffXferMode中的Mode.XOR 抑或,根据名称就知道,显示不相交的部分,目的在将水波纹的凹陷处去掉;

  2.组合水波纹和原型背景

  这里用到了PorterDuffXferMode中的Mode.SRC_ATOP,显示的是下方图层的全部和上方图层与下方图层相交的部分(蓝色的水),其实,你们一定会奇怪,你画就画吧,直接用canvas在控件上画不就好了么,干嘛要多此一举的使用一个midCanvas?

  这就得说道我所说的那个坑了,你直接在canvas上面画的话,设置的PorterDuffXferMode就不起作用,或者说,作用完全是混乱的(我在使用的时候,完全没有发现什么规律,有知道的朋友可以下方留言我),所以必须使用一块中间的画布,来将水波纹和圆组合后再次添加到canvas上,总结一下就是,要在onDraw(Canvas canvas)的canvas上话bitmap的话,最好直接跟它有关的bitmap只有一个;

  

  今天的分享就到这里,记录生活,共同进步!

   更新于 9-13 日, 上面所说的坑, 在看了一篇博文之后得到了解答, 直接在 onDraw(Canvas canvas) 方法中的 canvas 上今日 ‘作画’  的话, 画画的 DST 就是整个画布, 而并非只是你画在画布上的那一个图案,所以在使用混合模式 porterDuffXferMode 的时候,得到的图案就完全不是自己想要的了, 这时候可以像上面所说的, 将画作在令外的画布上,再画回来, 当然,更简单的方法就是使用离层缓冲, saveLayer();