共享单车地图的加载Maker

2017-10-19  本文已影响16人  ZHDelete

原版效果分析

origin_pic.png

分析总共有如下几部分:

还有一个就是 当停止loading的时候 有一个上下移动的抖动效果

实现效果:

loading_marker.gif

code:

1: 定义xml属性:

    <declare-styleable name="LoadingMarker">
        <attr name="darkClor" format="color" />
        <attr name="lightColor" format="color" />
        <attr name="isLoading" format="boolean" />
    </declare-styleable>

说明如下:

2: View的定义:

 private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

        //拿到 color
        TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LoadingMarker, defStyleAttr, 0);
        darkColor = ta.getColor(R.styleable.LoadingMarker_darkClor, Color.BLACK);
        lightColor = ta.getColor(R.styleable.LoadingMarker_lightColor, Color.BLUE);
        isLoading = ta.getBoolean(R.styleable.LoadingMarker_isLoading, false);
        ta.recycle();

        log(String.format("darkColor -> %d lightColor -> %d black -> %d darkColor -> %d", darkColor, lightColor, Color.BLACK, Color.BLUE));

        darkPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        darkPaint.setColor(darkColor);
        darkPaint.setStrokeWidth(4);

        lightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        lightPaint.setColor(lightColor);
        lightPaint.setStrokeWidth(4);

        ovalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        ovalPaint.setColor(lightColor);
        ovalPaint.setStrokeWidth(6);

        loadingAnim = ValueAnimator.ofFloat();
        loadingAnim.setFloatValues(0f, 360f);
        loadingAnim.setDuration(1000 * 1);
        loadingAnim.setRepeatMode(ValueAnimator.RESTART);
        loadingAnim.setRepeatCount(ValueAnimator.INFINITE);


        loadingAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                startAngle = (Float) animation.getAnimatedValue();
                log(String.format("startAngle -> %s", startAngle));
                postInvalidate();
            }
        });

        setLoading(isLoading);

    }

可以看到,我们先拿到了xml里的自定义属性:darkColor lightColor isLoading,
同时我们自定义了3个画笔,分别是
darkPaint 用来画背景的大圆形,
lightPaint 用来画前面的小圆形 ,
ovalPaint 用来画loading的弧形

接下来 定义了一个ValueAniator 用来生成 loading的圆弧的起始角度

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int defWidth = (int) (DisplayUtil.getScreenWidth(getContext()) * 0.9f / 13.0f);
        int defHeith = (int) (defWidth * 1.5f);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);


        if (widthMode == MeasureSpec.EXACTLY) {
            mWidth = widthSize;
        } else {
            mWidth = defWidth;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            mHeight = heightSize;
        } else {
            mHeight = defHeith;
        }

        log(String.format("mWidth -> %d mHeight -> %s", mWidth, mHeight));
        setMeasuredDimension(mWidth, mHeight);
    }

可以看到,当测量模式是不是EXACTLY(而是:AT_MOST 或者 UNSPECIFICED)时,我们规定了一个默认尺寸:

宽度是屏幕宽度的0.9/13,
高度是宽度的1.5倍,
这个可以在自己的使用过程中,按照UI的设计进行修改

 @Override
    protected void onDraw(final Canvas canvas) {
//        super.onDraw(canvas);
        //父类onDraw 空实现 注不注掉 都可

        int measureWidth = getMeasuredWidth();
        int measureHeight = getMeasuredHeight();

        final int centerX = measureWidth / 2;
        final int centerY = centerX;
        //背景 大圆 半径
        int radius = measureWidth / 2;
        //前景 小圆 半径
        int smallRadius = radius / 3;
        //loading 弧形 半径
        final int ovalRadius = radius * 2 / 3;
        //底座 椭圆 半径
        int ovalBotXRadius = smallRadius * 2 / 3;
        int ovalBotYRadius = smallRadius / 3;

//        log(String.format(
//                "measureWidth -> %d measureHeight -> %d\n" +
//                        "centerX -> %d centerY -> %d\n" +
//                        "radius -> %d smallRadius -> %d", measureWidth, measureHeight, centerX, centerY, radius, smallRadius));

        //底层实心 圆
        darkPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(centerX, centerY, radius, darkPaint);
        //上层 小圆
        lightPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(centerX, centerY, smallRadius, lightPaint);
        //画 底座
        RectF ovalBot = new RectF(centerX - ovalBotXRadius, measureHeight - ovalBotYRadius * 2, centerX + ovalBotXRadius, measureHeight);
        canvas.drawArc(ovalBot,0,360,true,lightPaint);
        //画 竖线
        canvas.drawLine(centerX, centerY * 2, centerX, measureHeight - ovalBotYRadius, darkPaint);

        ovalPaint.setStyle(Paint.Style.STROKE);
//        RectF oval = new RectF(centerX - ovalRadius, centerY - ovalRadius, centerX + ovalRadius, centerY + ovalRadius);
//        canvas.drawArc(oval, 90, 180, false, ovalPaint);

        if (isLoading) {
            RectF oval = new RectF(centerX - ovalRadius, centerY - ovalRadius, centerX + ovalRadius, centerY + ovalRadius);
            canvas.drawArc(oval, startAngle, 220, false, ovalPaint);

        }

    }

首先我们拿到 Viwe 的宽高,由于此时view已经经过了测量,因此,getMeasureWidth 和 getMeasureHeight 是可以取得值的.
然后我们计算出如下的值:

下面就是绘制的过程了:依次绘制了:

通过标志位isLoading,并提供相应的set get 方法来控制loading的操作.

    public boolean isLoading() {
        return isLoading;
    }

    public void setLoading(boolean loading) {
        isLoading = loading;
        if (loading) {
            if (!loadingAnim.isStarted()) {
                loadingAnim.start();
            }
        } else {
            loadingAnim.cancel();
            ViewCompat.offsetTopAndBottom(this,-30);
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    ViewCompat.offsetTopAndBottom(LoadingMarker.this, 30);
                }
            }, 200);
        }
        postInvalidate();
    }

如果想要进行loading动画,则loading = true,
半段了ValueAnimator 是否在执行,如果没有,则开始动画,
在动画进度的回调里,把动画当前进度,作为loading圆弧的起始角度,声明为全局变量

 loadingAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                startAngle = (Float) animation.getAnimatedValue();
                log(String.format("startAngle -> %s", startAngle));
                postInvalidate();
            }
        });

然后通过postInvalidate(); 让View重新绘制
在onDraw()发放里

   if (isLoading) {
            RectF oval = new RectF(centerX - ovalRadius, centerY - ovalRadius, centerX + ovalRadius, centerY + ovalRadius);
            canvas.drawArc(oval, startAngle, 220, false, ovalPaint);

        }

通过这两行代码,完成了 圆弧的绘制.

停止loading时

    public boolean isLoading() {
        return isLoading;
    }

    public void setLoading(boolean loading) {
        isLoading = loading;
        if (loading) {
            if (!loadingAnim.isStarted()) {
                loadingAnim.start();
            }
        } else {
            loadingAnim.cancel();
            ViewCompat.offsetTopAndBottom(this,-30);
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    ViewCompat.offsetTopAndBottom(LoadingMarker.this, 30);
                }
            }, 200);
        }
        postInvalidate();
    }

先停止了ValueAnimator,然后通过ViewCompatOffsetTopAndBottom(-30),让View整体上移30像素,随后又通过延迟post一个Runnable() 让view 下移30像素,这两个操作模拟一个刷新完毕抖动的想过.
当然也可以通过插值动画来进行一个更和谐美观的抖动.这个要留到以后慢慢完善了.

整体的实现大致如上,如有错误,请随时指出哈(手动滑稽...)

最后附上工程github地址

上一篇下一篇

猜你喜欢

热点阅读