Android应用中炫酷的横向和环形进度条的实例分享

一、概述
最近需要用进度条,秉着不重复造轮子的原则,上github上搜索了一番,看了几个觉得比较好看的ProgressBar,比如:daimajia的等。简单看了下代码,基本都是继承自View,彻彻底底的自定义了一个进度条。盯着那绚丽滚动条,忽然觉得,为什么要通过View去写一个滚动条,系统已经提供了ProgressBar以及属于它的特性,我们没必要重新去构建一个,但是系统的又比较丑,不同版本变现还不一定一样。那么得出我们的目标:改变系统ProgressBar的样子。
对没错,我们没有必要去从0打造一个ProgressBar,人家虽然长的不好看,但是特性以及稳定性还是刚刚的,我们只需要为其整下容就ok了。
接下来,我们贴下效果图:
1、横向的进度条

2、圆形的进度条

没错,这就是我们的进度条效果,横向的模仿了daimajia的进度条样子。不过我们继承子ProgressBar,简单的为其整个容,代码清晰易懂 。为什么说,易懂呢?
横向那个进度条,大家会drawLine()和drawText()吧,那么通过getWidth()拿到控件的宽度,再通过getProgress()拿到进度,按比例控制绘制线的长短,字的位置还不是分分钟的事。

二、实现
 横向的滚动条绘制肯定需要一些属性,比如已/未到达进度的颜色、宽度,文本的颜色、大小等。
 本来呢,我是想通过系统ProgressBar的progressDrawable,从里面提取一些属性完成绘制需要的参数的。但是,最终呢,反而让代码变得复杂。所以最终还是改用自定义属性。 说道自定义属性,大家应该已经不陌生了。
1、HorizontalProgressBarWithNumber
(1)自定义属性
values/attr_progress_bar.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources> 

  <declare-styleable name="HorizontalProgressBarWithNumber">
    <attr name="progress_unreached_color" format="color" />
    <attr name="progress_reached_color" format="color" />
    <attr name="progress_reached_bar_height" format="dimension" />
    <attr name="progress_unreached_bar_height" format="dimension" />
    <attr name="progress_text_size" format="dimension" />
    <attr name="progress_text_color" format="color" />
    <attr name="progress_text_offset" format="dimension" />
    <attr name="progress_text_visibility" format="enum">
      <enum name="visible" value="0" />
      <enum name="invisible" value="1" />
    </attr>
  </declare-styleable> 

  <declare-styleable name="RoundProgressBarWidthNumber">
    <attr name="radius" format="dimension" />
  </declare-styleable> 

</resources>

(2)构造中获取

public class HorizontalProgressBarWithNumber extends ProgressBar
{ 

  private static final int DEFAULT_TEXT_SIZE = 10;
  private static final int DEFAULT_TEXT_COLOR = 0XFFFC00D1;
  private static final int DEFAULT_COLOR_UNREACHED_COLOR = 0xFFd3d6da;
  private static final int DEFAULT_HEIGHT_REACHED_PROGRESS_BAR = 2;
  private static final int DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR = 2;
  private static final int DEFAULT_SIZE_TEXT_OFFSET = 10; 

  /**
   * painter of all drawing things
   */
  protected Paint mPaint = new Paint();
  /**
   * color of progress number
   */
  protected int mTextColor = DEFAULT_TEXT_COLOR;
  /**
   * size of text (sp)
   */
  protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE); 

  /**
   * offset of draw progress
   */
  protected int mTextOffset = dp2px(DEFAULT_SIZE_TEXT_OFFSET); 

  /**
   * height of reached progress bar
   */
  protected int mReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_REACHED_PROGRESS_BAR); 

  /**
   * color of reached bar
   */
  protected int mReachedBarColor = DEFAULT_TEXT_COLOR;
  /**
   * color of unreached bar
   */
  protected int mUnReachedBarColor = DEFAULT_COLOR_UNREACHED_COLOR;
  /**
   * height of unreached progress bar
   */
  protected int mUnReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR);
  /**
   * view width except padding
   */
  protected int mRealWidth; 

  protected boolean mIfDrawText = true; 

  protected static final int VISIBLE = 0; 

  public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs)
  {
    this(context, attrs, 0);
  } 

  public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs,
      int defStyle)
  {
    super(context, attrs, defStyle); 

    setHorizontalScrollBarEnabled(true); 

    obtainStyledAttributes(attrs); 

    mPaint.setTextSize(mTextSize);
    mPaint.setColor(mTextColor); 

  } 

  /**
   * get the styled attributes
   *
   * @param attrs
   */
  private void obtainStyledAttributes(AttributeSet attrs)
  {
    // init values from custom attributes
    final TypedArray attributes = getContext().obtainStyledAttributes(
        attrs, R.styleable.HorizontalProgressBarWithNumber); 

    mTextColor = attributes
        .getColor(
            R.styleable.HorizontalProgressBarWithNumber_progress_text_color,
            DEFAULT_TEXT_COLOR);
    mTextSize = (int) attributes.getDimension(
        R.styleable.HorizontalProgressBarWithNumber_progress_text_size,
        mTextSize); 

    mReachedBarColor = attributes
        .getColor(
            R.styleable.HorizontalProgressBarWithNumber_progress_reached_color,
            mTextColor);
    mUnReachedBarColor = attributes
        .getColor(
            R.styleable.HorizontalProgressBarWithNumber_progress_unreached_color,
            DEFAULT_COLOR_UNREACHED_COLOR);
    mReachedProgressBarHeight = (int) attributes
        .getDimension(
            R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height,
            mReachedProgressBarHeight);
    mUnReachedProgressBarHeight = (int) attributes
        .getDimension(
            R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height,
            mUnReachedProgressBarHeight);
    mTextOffset = (int) attributes
        .getDimension(
            R.styleable.HorizontalProgressBarWithNumber_progress_text_offset,
            mTextOffset); 

    int textVisible = attributes
        .getInt(R.styleable.HorizontalProgressBarWithNumber_progress_text_visibility,
            VISIBLE);
    if (textVisible != VISIBLE)
    {
      mIfDrawText = false;
    }
    attributes.recycle();
  }

嗯,看起来代码挺长,其实都是在获取自定义属性,没什么技术含量。

(3)onMeasure
刚才不是出onDraw里面写写就行了么,为什么要改onMeasure呢,主要是因为我们所有的属性比如进度条宽度让用户自定义了,所以我们的测量也得稍微变下。

@Override
protected synchronized void onMeasure(int widthMeasureSpec,
    int heightMeasureSpec)
{
  int heightMode = MeasureSpec.getMode(heightMeasureSpec); 

  if (heightMode != MeasureSpec.EXACTLY)
  { 

    float textHeight = (mPaint.descent() + mPaint.ascent());
    int exceptHeight = (int) (getPaddingTop() + getPaddingBottom() + Math
        .max(Math.max(mReachedProgressBarHeight,
            mUnReachedProgressBarHeight), Math.abs(textHeight))); 

    heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,
        MeasureSpec.EXACTLY);
  }
  super.onMeasure(widthMeasureSpec, heightMeasureSpec); 

}

宽度我们不变,所以的自定义属性不涉及宽度,高度呢,只考虑不是EXACTLY的情况(用户明确指定了,我们就不管了),根据padding和进度条宽度算出自己想要的,如果非EXACTLY下,我们进行exceptHeight封装,传入给控件进行测量高度。
测量完,就到我们的onDraw了~~~
(4)onDraw

@Override
  protected synchronized void onDraw(Canvas canvas)
  {
    canvas.save();
    //画笔平移到指定paddingLeft, getHeight() / 2位置,注意以后坐标都为以此为0,0
    canvas.translate(getPaddingLeft(), getHeight() / 2); 

    boolean noNeedBg = false;
    //当前进度和总值的比例
    float radio = getProgress() * 1.0f / getMax();
    //已到达的宽度
    float progressPosX = (int) (mRealWidth * radio);
    //绘制的文本
    String text = getProgress() + "%"; 

    //拿到字体的宽度和高度
    float textWidth = mPaint.measureText(text);
    float textHeight = (mPaint.descent() + mPaint.ascent()) / 2; 

    //如果到达最后,则未到达的进度条不需要绘制
    if (progressPosX + textWidth > mRealWidth)
    {
      progressPosX = mRealWidth - textWidth;
      noNeedBg = true;
    } 

    // 绘制已到达的进度
    float endX = progressPosX - mTextOffset / 2;
    if (endX > 0)
    {
      mPaint.setColor(mReachedBarColor);
      mPaint.setStrokeWidth(mReachedProgressBarHeight);
      canvas.drawLine(0, 0, endX, 0, mPaint);
    } 

    // 绘制文本
    if (mIfDrawText)
    {
      mPaint.setColor(mTextColor);
      canvas.drawText(text, progressPosX, -textHeight, mPaint);
    } 

    // 绘制未到达的进度条
    if (!noNeedBg)
    {
      float start = progressPosX + mTextOffset / 2 + textWidth;
      mPaint.setColor(mUnReachedBarColor);
      mPaint.setStrokeWidth(mUnReachedProgressBarHeight);
      canvas.drawLine(start, 0, mRealWidth, 0, mPaint);
    } 

    canvas.restore(); 

  } 

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh)
  {
    super.onSizeChanged(w, h, oldw, oldh);
    mRealWidth = w - getPaddingRight() - getPaddingLeft(); 

  }

其实核心方法就是onDraw了,但是呢,onDraw也很简单,绘制线、绘制文本、绘制线,结束。

还有两个简单的辅助方法:

/**
 * dp 2 px
 *
 * @param dpVal
 */
protected int dp2px(int dpVal)
{
  return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
      dpVal, getResources().getDisplayMetrics());
} 

/**
 * sp 2 px
 *
 * @param spVal
 * @return
 */
protected int sp2px(int spVal)
{
  return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
      spVal, getResources().getDisplayMetrics()); 

}

好了,到此我们的横向进度就结束了,是不是很简单~~如果你是自定义View,你还得考虑progress的更新,考虑状态的销毁与恢复等等复杂的东西。
接下来看我们的RoundProgressBarWidthNumber圆形的进度条。

2、RoundProgressBarWidthNumber   
圆形的进度条和横向的进度条基本变量都是一致的,于是我就让RoundProgressBarWidthNumber extends HorizontalProgressBarWithNumber 了。
然后需要改变的就是测量和onDraw了:
完整代码:

package com.zhy.view; 

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.util.AttributeSet; 

import com.zhy.library.view.R; 

public class RoundProgressBarWidthNumber extends
    HorizontalProgressBarWithNumber {
  /**
   * mRadius of view
   */
  private int mRadius = dp2px(30); 

  public RoundProgressBarWidthNumber(Context context) {
    this(context, null);
  } 

  public RoundProgressBarWidthNumber(Context context, AttributeSet attrs) {
    super(context, attrs); 

    mReachedProgressBarHeight = (int) (mUnReachedProgressBarHeight * 2.5f);
    TypedArray ta = context.obtainStyledAttributes(attrs,
        R.styleable.RoundProgressBarWidthNumber);
    mRadius = (int) ta.getDimension(
        R.styleable.RoundProgressBarWidthNumber_radius, mRadius);
    ta.recycle(); 

    mTextSize = sp2px(14); 

    mPaint.setStyle(Style.STROKE);
    mPaint.setAntiAlias(true);
    mPaint.setDither(true);
    mPaint.setStrokeCap(Cap.ROUND); 

  } 

  @Override
  protected synchronized void onMeasure(int widthMeasureSpec,
      int heightMeasureSpec) {
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec); 

    int paintWidth = Math.max(mReachedProgressBarHeight,
        mUnReachedProgressBarHeight); 

    if (heightMode != MeasureSpec.EXACTLY) { 

      int exceptHeight = (int) (getPaddingTop() + getPaddingBottom()
          + mRadius * 2 + paintWidth);
      heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,
          MeasureSpec.EXACTLY);
    }
    if (widthMode != MeasureSpec.EXACTLY) {
      int exceptWidth = (int) (getPaddingLeft() + getPaddingRight()
          + mRadius * 2 + paintWidth);
      widthMeasureSpec = MeasureSpec.makeMeasureSpec(exceptWidth,
          MeasureSpec.EXACTLY);
    } 

    super.onMeasure(heightMeasureSpec, heightMeasureSpec); 

  } 

  @Override
  protected synchronized void onDraw(Canvas canvas) { 

    String text = getProgress() + "%";
    // mPaint.getTextBounds(text, 0, text.length(), mTextBound);
    float textWidth = mPaint.measureText(text);
    float textHeight = (mPaint.descent() + mPaint.ascent()) / 2; 

    canvas.save();
    canvas.translate(getPaddingLeft(), getPaddingTop());
    mPaint.setStyle(Style.STROKE);
    // draw unreaded bar
    mPaint.setColor(mUnReachedBarColor);
    mPaint.setStrokeWidth(mUnReachedProgressBarHeight);
    canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
    // draw reached bar
    mPaint.setColor(mReachedBarColor);
    mPaint.setStrokeWidth(mReachedProgressBarHeight);
    float sweepAngle = getProgress() * 1.0f / getMax() * 360;
    canvas.drawArc(new RectF(0, 0, mRadius * 2, mRadius * 2), 0,
        sweepAngle, false, mPaint);
    // draw text
    mPaint.setStyle(Style.FILL);
    canvas.drawText(text, mRadius - textWidth / 2, mRadius - textHeight,
        mPaint); 

    canvas.restore(); 

  } 

}

首先获取它的专有属性mRadius,然后根据此属性去测量,测量完成绘制;
绘制的过程呢?
先绘制一个细一点的圆,然后绘制一个粗一点的弧度,二者叠在一起就行。文本呢,绘制在中间~~~总体,没什么代码量。
好了,两个进度条就到这了,是不是发现简单很多。总体设计上,存在些问题,如果抽取一个BaseProgressBar用于获取公共的属性;然后不同样子的进度条继承分别实现自己的测量和样子,这样结构可能会清晰些~~~

三、使用
布局文件

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  xmlns:zhy="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent" > 

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="25dp" > 

    <com.zhy.view.HorizontalProgressBarWithNumber
      android:id="@+id/id_progressbar01"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginTop="50dip"
      android:padding="5dp" /> 

    <com.zhy.view.HorizontalProgressBarWithNumber
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginTop="50dip"
      android:padding="5dp"
      android:progress="50"
      zhy:progress_text_color="#ffF53B03"
      zhy:progress_unreached_color="#ffF7C6B7" /> 

    <com.zhy.view.RoundProgressBarWidthNumber
      android:id="@+id/id_progress02"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginTop="50dip"
      android:padding="5dp"
      android:progress="30" /> 

    <com.zhy.view.RoundProgressBarWidthNumber
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginTop="50dip"
      android:padding="5dp"
      android:progress="50"
      zhy:progress_reached_bar_height="20dp"
      zhy:progress_text_color="#ffF53B03"
      zhy:radius="60dp" /> 

  </LinearLayout> 

</ScrollView>

MainActivity

package com.zhy.sample.progressbar; 

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler; 

import com.zhy.annotation.Log;
import com.zhy.view.HorizontalProgressBarWithNumber; 

public class MainActivity extends Activity { 

  private HorizontalProgressBarWithNumber mProgressBar;
  private static final int MSG_PROGRESS_UPDATE = 0x110; 

  private Handler mHandler = new Handler() {
    @Log
    public void handleMessage(android.os.Message msg) {
      int progress = mProgressBar.getProgress();
      mProgressBar.setProgress(++progress);
      if (progress >= 100) {
        mHandler.removeMessages(MSG_PROGRESS_UPDATE); 

      }
      mHandler.sendEmptyMessageDelayed(MSG_PROGRESS_UPDATE, 100);
    };
  }; 

  @Log
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mProgressBar = (HorizontalProgressBarWithNumber) findViewById(R.id.id_progressbar01);
    mHandler.sendEmptyMessage(MSG_PROGRESS_UPDATE); 

  } 

}
(0)

相关推荐

  • Android实现计步进度的环形Progress

    项目中需要实现一个计步进度的环形Progress,当未达到设定目标时,绘制特定弧度((已实现步数/目标步数)*360°)的圆弧.当已实现步数大于等于目标步数时绘制整个360°圆环. 效果图: 代码实现: 设置已完成步数和目标步数: public void setStep(int stepDone, int stepGoal) { this.stepDone = stepDone; this.stepGoal = stepGoal; int progess = (stepDone * 100) /

  • Android中制作进度框和环形进度条的简单实例分享

    进度框 import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import java.util.Random; public class ObliqueProgressbar ext

  • Android实现环形进度条的实例

    Android实现环形进度条的效果图如下: 自定义控件:AttendanceProgressBar 代码如下: public class AttendanceProgressBar extends View { // 画圆环底部的画笔 private Paint mCirclePaint; // 画圆环的画笔 private Paint mRingPaint; // 画字体的画笔 private Paint mTextPaint; // 圆形颜色 private int mCircleColor

  • Android自定义环形LoadingView效果

    最近项目有要用到环形的进度条,Github上有一个类似的DashedCircularProgress控件,但是他画的进度是通过设置画笔的虚线效果来实现间隔的:progressPaint.setPathEffect(new DashPathEffect(new float[]{dashWith, dashSpace}, dashSpace));如果内层还有一层圆环,在动态设置时,内层和外层有细微的偏差.于是我在原有基础上改了一个,实现了我要的效果(设置进度时可以选择加动画或者不加动画): 控件实现

  • Android实现环形进度条代码

    本文参考借鉴:http://www.jb51.net/article/102983.htm 先上效果图: 自定义控件:AttendanceProgressBar 代码如下: public class AttendanceProgressBar extends View { // 画圆环底部的画笔 private Paint mCirclePaint; // 画圆环的画笔 private Paint mRingPaint; // 画字体的画笔 private Paint mTextPaint; /

  • Android实现创意LoadingView动画效果

    Android上的热火锅煮萝卜蔬菜的Loading动画效果. 这是一个锅煮萝卜的Loading动画,效果仿照自之前IOS上看到的一个效果,觉得挺有意思,就移植过来了,在此完成了Dialog的样式,方便使用者作为LoadingView去使用. 关键性代码: package yellow5a5.demo.boilingloadingview.View; import android.animation.Animator; import android.animation.AnimatorListen

  • Android 自定义通用的loadingview实现代码

    功能 1.显示加载视图,加载失败的时候显示加载失败视图,数据为空时显示数据为空视图,支持为失败视图设置点击事件重新加载数据. 2.支持个性化设置,自定义设置 加载.失败.空数据视图. 先放一张效果图压压惊 实现 实现思路其实就是一个FrameLayout里添加三个布局做处理显示隐藏,自定义视图其实就是替换里面的view ,代码比较简单,如果直接看过我的自定义view系列文章,或者对自定义view有所了解,都很容易看懂,所有直接上代码了. 具体代码 Java 代码 public class Com

  • Android项目实战手把手教你画圆形水波纹loadingview

    本文实例讲解的是如何画一个满满圆形水波纹loadingview,这类效果应用场景很多,比如内存占用百分比之类的,分享给大家供大家参考,具体内容如下 效果图如下: 预备的知识: 1.贝塞尔曲线    如果你不了解,可以来这里进行基础知识储备:神奇的贝塞尔曲线 2.Paint.setXfermode()  以及PorterDuffXfermode 千万不要被这个b的名字吓到,不熟悉看到可能会认为很难记,其实 只要站在巨人的丁丁上 还是很简单的. 好了 废话不多说 ,跟我一步步来做一个炫酷的view吧

  • Android自定义View实现环形进度条的思路与实例

    前言 前段时间看到了豆瓣FM的音乐播放界面,有一个环形的进度条,非常的好看,于是想了想,为什么不自己做一个呢,于是就开始了自定义的过程 豆瓣FM的播放界面如下图: 功能分析 虽然功能比较简单,但是仍然需要仔细分析 1.图标外还有一圈圆圈,可以设置宽度 2.圆形进度条和进度条底部,可以设置宽度,颜色等 3.内部有一个圆形图片,可旋转 实现思路分析 1.可以设置宽度的圆圈 这个比较容易,直接在onDraw方法中使用canvas绘制即可,当然,在间距和半径的处理上需要仔细,控件本体其实还是一个长方形,

  • Android环形进度条(安卓默认形式)实例代码

    Android开发中,有很多的功能在实际应用中都起了很大的作用,比如android进度条的实现方式,下面给大家介绍Android环形进度条(安卓默认形式),具体内容如下所示: .xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_widt

随机推荐