Android 自定义View UC下拉刷新效果(一)

啦啦啦,今天给大家带来最近弄的CircleProgress相关的效果。这里的效果图可能还看不出是UC浏览器的那个下拉刷新的效果,不过首先还是要说说这个进度条,在下一篇中将实现真正的下拉刷新!
话不多说,直接上图:

progress_静态.png
progress_动态.gif

特点:就是一个进度条

1、可以设置多种颜色。
2、可以显示多种状态(LOADING、SUCCESS、ERROR,其实远不止这几种)
3、可以控制是否显示箭头

相关准备工作

知识点:

1.Canvas里面相关方法
2.drawArc()画圆弧的方法
3.drawPath()画路径的方法
4.属性动画使用

结果的钩钩或者那个叉叉还有那个箭头都是使用drawPath()来完成的。

在onDraw里面对应有四个相关的方法:

1.drawArc(Canvas canvas):画对应的进度
2.drawTriangle(Canvas c, float startAngle, float sweepAngle):画箭头
3.drawHook(Canvas canvas):画钩钩
4.drawError(Canvas canvas):画叉叉

三个动画控制:

两个来控制进度条的 startAnglesweepAngle,一个用来控制画钩钩或者画叉叉的时候的渐变效果!

相关代码

三支画笔
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeCap(Cap.ROUND);
    mPaint.setStrokeWidth(mBorderWidth);
    mPaint.setColor(mColors[mCurrentColorIndex]);

    mHookPaint = new Paint(mPaint);
    mArrowPaint = new Paint(mPaint);
三个动画
  private void setupAnimations() {
    mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f);
    mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
    mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
    mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
    mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);

    mObjectAnimatorSweep = ObjectAnimator.ofFloat(this, mSweepProperty, 360f - MIN_SWEEP_ANGLE * 2);
    mObjectAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR);
    mObjectAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION);
    mObjectAnimatorSweep.setRepeatMode(ValueAnimator.RESTART);
    mObjectAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE);
    mObjectAnimatorSweep.addListener(new SimpleAnimatorListener() {
        @Override
        public void onAnimationRepeat(Animator animation) {
            toggleAppearingMode();
        }
    });
    fractionAnimator = ValueAnimator.ofInt(0, 255);
    fractionAnimator.setInterpolator(ANGLE_INTERPOLATOR);
    fractionAnimator.setDuration(100);
    fractionAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            fraction = animation.getAnimatedFraction();
            mHookPaint.setAlpha((Integer) animation.getAnimatedValue());
            invalidate();
        }
    });
}
四个draw相关方法
 private void drawError(Canvas canvas) {
    mError.reset();
    mError.moveTo(fBounds.centerX() + fBounds.width() * 0.2f * fraction, fBounds.centerY() - fBounds.height() * 0.2f * fraction);
    mError.lineTo(fBounds.centerX() - fBounds.width() * 0.2f * fraction, fBounds.centerY() + fBounds.height() * 0.2f * fraction);
    mError.moveTo(fBounds.centerX() - fBounds.width() * 0.2f * fraction, fBounds.centerY() - fBounds.height() * 0.2f * fraction);
    mError.lineTo(fBounds.centerX() + fBounds.width() * 0.2f * fraction, fBounds.centerY() + fBounds.height() * 0.2f * fraction);
    mHookPaint.setColor(mColors[3]);
    canvas.drawPath(mError, mHookPaint);
    canvas.drawArc(fBounds, 0, 360, false, mHookPaint);
}

private void drawHook(Canvas canvas) {
    mHook.reset();
    mHook.moveTo(fBounds.centerX() - fBounds.width() * 0.25f * fraction, fBounds.centerY());
    mHook.lineTo(fBounds.centerX() - fBounds.width() * 0.1f * fraction, fBounds.centerY() + fBounds.height() * 0.18f * fraction);
    mHook.lineTo(fBounds.centerX() + fBounds.width() * 0.25f * fraction, fBounds.centerY() - fBounds.height() * 0.20f * fraction);
    mHookPaint.setColor(mColors[0]);
    canvas.drawPath(mHook, mHookPaint);
    canvas.drawArc(fBounds, 0, 360, false, mHookPaint);

}

private void drawArc(Canvas canvas) {
    float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;
    float sweepAngle = mCurrentSweepAngle;
    if (mModeAppearing) {
        mPaint.setColor(gradient(mColors[mCurrentColorIndex], mColors[mNextColorIndex],
                mCurrentSweepAngle / (360 - MIN_SWEEP_ANGLE * 2)));
        sweepAngle += MIN_SWEEP_ANGLE;
    } else {
        startAngle = startAngle + sweepAngle;
        sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE;
    }
    canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint);
    if (showArrow) {
        drawTriangle(canvas, startAngle, sweepAngle);
    }
}

public void drawTriangle(Canvas c, float startAngle, float sweepAngle) {
    if (mArrow == null) {
        mArrow = new Path();
        mArrow.setFillType(Path.FillType.EVEN_ODD);
    } else {
        mArrow.reset();
    }

    float x = (float) (mRingCenterRadius * Math.cos(0) + fBounds.centerX());
    float y = (float) (mRingCenterRadius * Math.sin(0) + fBounds.centerY());
    mArrow.moveTo(0, 0);
    mArrow.lineTo(ARROW_WIDTH * mArrowScale, 0);
    mArrow.lineTo((ARROW_WIDTH * mArrowScale / 2), (ARROW_HEIGHT
            * mArrowScale));
    mArrow.offset(x, y);
    mArrow.close();
    c.rotate(startAngle + sweepAngle, fBounds.centerX(),
            fBounds.centerY());
    c.drawPath(mArrow, mPaint);
}

上面的代码就是相关核心的方法了,其实对应的进度条效果就是控制 startAnglesweepAngle这两个对应的字段,然后不断的调用drawArc()方法。

对于画钩钩或者画叉叉,就是一个ValueAnimator,通过百分比控制缩放和画笔的透明度。

对于drawTriangle()方法,如果你觉得很眼熟的话也很正常,其实这个就是在SwipeRefreshLayout里面抄过来的。。。。。
一开始,我很纠结这个箭头怎么才能跟着进度条一起旋转,自己写的也是有各种问题,另外mArrowScale这个参数在里面其实没有使用的。
如果没有offset偏移量,那么那个path肯定是画在左上角的。

x=mRingCenterRadius +fBounds.centerX();
y=fBounds.centerY();

通过这个一设置,这个path其实就到了右边的中间靠着圆弧的内侧一点去了(因为这里的半径减去了圆弧自己的宽度。。),这么一来,再根据相关的角度旋转角度,就有一种跟着进度条一直转的效果了!

对于drawArc()方法,主要是控制startAnglesweepAngle这两个变量,mCurrentGlobalAngle的变化范围是(0360),而`mCurrentSweepAngle`的变化范围是(0360f - MIN_SWEEP_ANGLE * 2),为什么要减去两个最小值呢?因为sweepAngle总会加一个或者总会减去一个最小值,所以最小间距还是MIN_SWEEP_ANGLE
至于什么时候加什么时候减呢?这里有一个变量值mModeAppearing提供记录!那就是当mObjectAnimatorSweep的动画重复的时候,就需要切换一下了。。

 mObjectAnimatorSweep.addListener(new SimpleAnimatorListener() {
        @Override
        public void onAnimationRepeat(Animator animation) {
            toggleAppearingMode();
        }
    });
private void toggleAppearingMode() {
    mModeAppearing = !mModeAppearing;
    if (mModeAppearing) {
        mCurrentColorIndex = ++mCurrentColorIndex % 4;
        mNextColorIndex = ++mNextColorIndex % 4;
        mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;
    }
}

最终效果图

pull_refresh2.gif

下一篇Android 自定义View UC下拉刷新效果(二)
介绍剩余的下拉刷新部分,还有就是两个圆圈的过度效果。。

相关的代码请移步 我的github。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容