美文网首页
共享单车地图的加载Maker

共享单车地图的加载Maker

作者: ZHDelete | 来源:发表于2017-10-19 14:50 被阅读16次

    原版效果分析

    origin_pic.png

    分析总共有如下几部分:

    • 背景的深色大圆形
    • 前景的浅色的小圆形
    • 垂直的竖线
    • 底部的椭圆形的底座
    • loading 时转圈的弧形

    还有一个就是 当停止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>
    

    说明如下:

    • darkColor: 深色大圆颜色,及竖线颜色
    • lightColor: 浅色的小圆的颜色,及底部椭圆的颜色
    • isLoading: 是否转圈圈

    2: View的定义:

    • 2.1: 构造方法,初始化需要的变量:
     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的圆弧的起始角度

    • 2.2: 测量,View大小的确定:
     @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的设计进行修改

    • 2.3: 绘制View的内容:
     @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 是可以取得值的.
    然后我们计算出如下的值:

    • centerX: 大圆形的圆心x,为view宽度的一半

    • centerY: 大圆形的圆心y,同圆心的x坐标

    • radius: 大圆形的半径,为view宽度的一半

    • smallRadius: 前面小圆形的半径 为大圆形半径的1/3

    • ovalRadius: loading的圆弧的半径,为大圆半径的2/3

    • ovalBotXRadius: 底部小椭圆的长轴半径,为小圆形的半径的2/3

    • ovalBotYRadius: 底部小椭圆的短轴半径,为小圆形半径的1/3,即其长轴半径的一半

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

    • 背景的实心大圆形

    • 上层的小圆形

    • 底座的小椭圆形

    • 竖直的线

    • loading的圆弧
      这里要注意,需要先画椭圆形底座,再画竖线,营造一个遮挡的关系

    • 2.3: 如何实现的转圈圈:

    通过标志位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地址

    相关文章

      网友评论

          本文标题:共享单车地图的加载Maker

          本文链接:https://www.haomeiwen.com/subject/pqduuxtx.html