美文网首页
共享单车地图的加载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

    原版效果分析 分析总共有如下几部分: 背景的深色大圆形 前景的浅色的小圆形 垂直的竖线 底部的椭圆形的底座 loa...

  • 地图导航应用:百度地图与高德地图竞品分析

    共享单车的火爆为地图类应用带来了崭新的使用场景,百度地图抓住时机地增加了共享单车的功能入口,同时新增了“地铁+共享...

  • 高德地图的一些你可能不知道的事之一

    共享单车从去年到今年成为了人类不可或缺的一部分,其中很多单车的app中都集成了地图,本人的公司也是做单车共享的,所...

  • 继续上一篇

    通过科技手段的不断运用与提升,进一步提升共享单车的便利程度。科技人员可以开发专门的共享单车地图,通过准确的定位系统...

  • 共享单车之路在何方…

    住的地方刚来的时候没有共享单车,也就是在过年之后,具体来说应该是五一之后才加入到共享单车的蓝色范围内,在地图上可以...

  • 从共享单车浅谈互联网对于国民性的影响

    今天外出想起来骑共享单车,地图上看到摩拜单车的显示,发现地图中有显示 五六辆车,但是四周寻找之后却发现,并没有那么...

  • 共享单车还能火多久?

    ** 什么是共享单车 **现在提到共享单车,大家应该都不陌生,距离共享单车进入市场已有段时间。共享单车是共享经济的...

  • 共享单车合集(首页地图篇)

    自2014年共享单车出来以后共享单车热门话题就一直没有断过,大家对共享单车的说法也是褒贬不一,而对于从事iOS开发...

  • 共享XX

    共享电单车 芒果电单车 七号电单车 共享汽车 gofun 共享单车 摩拜 ofo bluegogo

  • 骑单车找红包选一款最好用的手机地图

    骑单车找红包选一款最好用的手机地图 随着摩拜、OfO等共享单车在城市大街小巷流行,大大提高了人们使用单车的频率。不...

网友评论

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

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