业余时间:项目的规划与准备

前言


思乡心切啊!还有三天就可以回家过年了!

“业余时间”是自己写的一个APP,最初学习Android的时候因为很闲,所以听从师父的建议,利用业余时间把公司的项目重写了一遍(大部分主要功能),之后开始写自己的APP,以为了快速提高技术。这个APP很简陋,只是一些图片和文字的列表,还有设置功能和毫无卵用的启动密码保护功能,虽然现在来看里面的代码写的惨不忍睹,但是不可否认的是,自己当时确实也通过写这个APP学到了不少东西,如:列表的下拉刷新与加载更多的逻辑,Android5.0的新控件,图片列表在未知图片宽高时的加载优化等等,甚至为了模仿探探和网易云音乐一个很好看的功能,学会了反编译和抓包。这也是为什么最近想开始重写这个APP的原因,就像任玉刚说的,我们不可能通过公司的项目学到Android的每个知识点和新技术,那么业余时间的学习就显得很重要。况且公司的项目大多数情况下也不是我们想怎么改就怎么改的,leader会让你把okhttp改为Retrofit?会让你把SharedPreferences改为GreenDao3?会让你把MVC改为MVP?有太多的新技术不是一个公司的项目就全能用到的。

头部标识


一个APP怎么可能没有列表呢,既然有列表那么就来个个性化的下拉刷新提示与加载提示吧!最终效果图如下

刷新头部时钟的关键代码:
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        size = Math.min(getWidth(), getHeight());
        int factor = 524;//默认624
        sharpRadius = size * 30.0f / (factor * 2.0f);
        otherRadius = size * 18.0f / (factor * 2.0f);
        outsideCircleWidth = size * 25.0f / factor;
        outsideDistance = size * 95.0f / factor;
        hourLength = size * 180.0f / factor;
        minuteLength = size * 110.0f / factor;
        needleWidth = size * 35.0f / (factor * 2.0f);
        centerCircleRadius = needleWidth;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //画背景和外圆
        canvas.drawColor(Color.TRANSPARENT);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(outsideCircleWidth);
        canvas.drawCircle(size / 2, size / 2, (size - outsideCircleWidth) / 2, paint);
        //画时间圆点
        paint.setStyle(Paint.Style.FILL);
        for (int i = 0; i < 12; i++) {
            if (i % 3 == 0) {
                canvas.drawCircle(size / 2, outsideDistance, sharpRadius, paint);
            } else {
                canvas.drawCircle(size / 2, outsideDistance, otherRadius, paint);
            }
            canvas.rotate(30, size / 2, size / 2);
        }

        paint.setStrokeWidth(needleWidth);
        //画时针
        canvas.drawLine(size / 2, size / 2, getX(minuteLength, angle), getY(minuteLength, angle), paint);
        //画分针
        int hourAngle = (angle % 30) * 12 - 90;
        canvas.drawLine(size / 2, size / 2, getX(hourLength, hourAngle), getY(hourLength, hourAngle), paint);

        //画中心圆点
        canvas.drawCircle(size / 2, size / 2, centerCircleRadius, paint);
    }

    private float getX(float radius, int angle) {
        return (float) (size / 2 + radius * Math.cos(angle * 3.14 / 180));
    }

    private float getY(float radius, int angle) {
        return (float) (size / 2 + radius * Math.sin(angle * 3.14 / 180));
    }
时钟的原图是从网上找的,在onSizeChanged方法中计算始终各个部位相对于时钟大小的比例,在onDraw中画出时钟就可以了。为了响应下拉的距离,对外暴露一个实时设置角度的方法:
    public void setClockAngle(int angle) {
        angle /= 4;
        this.angle = angle - 90;
        invalidate();
    }
这里的angle参数实际上就是刷新头部的高度。正在刷新的动画用ValueAnimator轻松实现:
    public void startClockAnim() {
        if (animator == null) {
            animator = ValueAnimator.ofInt(angle, angle + 360);
            animator.setRepeatMode(ValueAnimator.RESTART);
            animator.setRepeatCount(ValueAnimator.INFINITE);
            animator.setDuration(10000).setInterpolator(new LinearInterpolator());
            animator.start();
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    angle = (int) animation.getAnimatedValue();
                    invalidate();
                }
            });
        } else {
            animator.setIntValues(angle, angle + 360);
            animator.start();
        }
    }

    public void stopClockAnim() {
        if (animator != null) {
            animator.cancel();
        }
    }
这里起始的角度就是手指松开时时钟对应的角度,动画数值为时针的角度,通过时针角度可以算出分针角度,这里利用setIntValues(int... values)方法重新设置起始角度,而非每次新创建ValueAnimator。OK,刷新头部的刷新标识就实现了。

尾部标识


尾部标识动画的关键在于角度的计算,这是之前的项目的一个动画,当时是射击师给的图用帧动画实现的,后来发现图片的适配有问题就改用自定义View实现了,当时在写的时候角度算了好久就是不对,后来想到之前一个IOS同事的说过的话,动画的实现得一步一步来,所以当我把它拆解为半圆的角度渐增动画和半圆的旋转动画时发现,其实还是很简单的:
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        rectF.set(outsizeWidth / 2, outsizeWidth / 2, getWidth() - outsizeWidth / 2, getHeight() - outsizeWidth / 2);
        float space = outsizeWidth + insideWidth * 3 / 2;
        rectFInside.set(space, space, getWidth() - space, getHeight() - space);
        circleAnimate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        angle = angle == 0 ? 1 : angle;
        canvas.drawArc(rectF, rotateAngle - angle / 2, angle, false, paint);
        canvas.drawArc(rectFInside, rotateAngle + 180 - angle / 2, angle, false, paintInside);
    }

    private void circleAnimate() {
        if (animator == null) {
            animator = ValueAnimator.ofInt(0, maxAngle);
            animator.setDuration(duration);
            animator.setRepeatMode(ValueAnimator.REVERSE);
            animator.setRepeatCount(ValueAnimator.INFINITE);
            animator.setInterpolator(new LinearInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    angle = (int) animation.getAnimatedValue();
                    invalidate();
                }
            });
            animatorRotate = ValueAnimator.ofInt(0, 360);
            animatorRotate.setDuration(duration);
            animatorRotate.setRepeatMode(ValueAnimator.RESTART);
            animatorRotate.setRepeatCount(ValueAnimator.INFINITE);
            animatorRotate.setInterpolator(new LinearInterpolator());
            animatorRotate.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    rotateAngle = (int) animation.getAnimatedValue();
                }
            });
        }
        if (!animator.isRunning()) {
            animator.start();
            animatorRotate.start();
        }
    }

End


刷新标识和加载已经实现,接下来只需要放在刷新头部和加载尾部的布局中即可,业余时间项目中的列表通过对RecyclerView进一步封装来实现。是的,网上能找出很多这样的封装控件,不过,还是那句话:那是别人的轮子!RecyclerView的进一步封装放在下一篇博客吧!


新年快乐!鸡年大吉吧!





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值