返回介绍

十一 - 仿水波纹流量球进度条控制器,实现高端大气的主流特效

发布于 2025-02-28 12:56:41 字数 16850 浏览 0 评论 0 收藏 0


今天看到一个效果挺不错的,就模仿了下来,加上了一些自己想要的效果,感觉还不错的样子,所以就分享出来了,话不多说,上图

截图

CircleView

这里主要是实现中心圆以及水波特效

package com.lgl.circleview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ProgressBar;

/**
 * 水波圆
 * 
 * @author lgl
 * 
 */
public class CircleView extends View {

  private Context mContext;

  private int mScreenWidth;
  private int mScreenHeight;

  private Paint mRingPaint;
  private Paint mCirclePaint;
  private Paint mWavePaint;
  private Paint linePaint;
  private Paint flowPaint;
  private Paint leftPaint;

  private int mRingSTROKEWidth = 15;
  private int mCircleSTROKEWidth = 2;
  private int mLineSTROKEWidth = 1;

  private int mCircleColor = Color.WHITE;
  private int mRingColor = Color.WHITE;
  private int mWaveColor = Color.WHITE;

  private Handler mHandler;
  private long c = 0L;
  private boolean mStarted = false;
  private final float f = 0.033F;
  private int mAlpha = 50;// 透明度
  private float mAmplitude = 10.0F; // 振幅
  private float mWaterLevel = 0.5F;// 水高(0~1)
  private Path mPath;

  // 绘制文字显示在圆形中间,只是我没有设置,我觉得写在布局上也挺好的
  private String flowNum = "";
  private String flowLeft = "还剩余";

  /**
   * @param context
   */
  public CircleView(Context context) {
    super(context);
    // TODO Auto-generated constructor stub
    mContext = context;
    init(mContext);
  }

  /**
   * @param context
   * @param attrs
   */
  public CircleView(Context context, AttributeSet attrs) {
    super(context, attrs);
    // TODO Auto-generated constructor stub
    mContext = context;
    init(mContext);
  }

  /**
   * @param context
   * @param attrs
   * @param defStyleAttr
   */
  public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    // TODO Auto-generated constructor stub
    mContext = context;
    init(mContext);
  }

  public void setmWaterLevel(float mWaterLevel) {
    this.mWaterLevel = mWaterLevel;
  }

  private void init(Context context) {
    mRingPaint = new Paint();
    mRingPaint.setColor(mRingColor);
    mRingPaint.setAlpha(50);
    mRingPaint.setStyle(Paint.Style.STROKE);
    mRingPaint.setAntiAlias(true);
    mRingPaint.setStrokeWidth(mRingSTROKEWidth);

    mCirclePaint = new Paint();
    mCirclePaint.setColor(mCircleColor);
    mCirclePaint.setStyle(Paint.Style.STROKE);
    mCirclePaint.setAntiAlias(true);
    mCirclePaint.setStrokeWidth(mCircleSTROKEWidth);

    linePaint = new Paint();
    linePaint.setColor(mCircleColor);
    linePaint.setStyle(Paint.Style.STROKE);
    linePaint.setAntiAlias(true);
    linePaint.setStrokeWidth(mLineSTROKEWidth);

    flowPaint = new Paint();
    flowPaint.setColor(mCircleColor);
    flowPaint.setStyle(Paint.Style.FILL);
    flowPaint.setAntiAlias(true);
    flowPaint.setTextSize(36);

    leftPaint = new Paint();
    leftPaint.setColor(mCircleColor);
    leftPaint.setStyle(Paint.Style.FILL);
    leftPaint.setAntiAlias(true);
    leftPaint.setTextSize(36);

    mWavePaint = new Paint();
    mWavePaint.setStrokeWidth(1.0F);
    mWavePaint.setColor(mWaveColor);
    mWavePaint.setAlpha(mAlpha);
    mPath = new Path();

    mHandler = new Handler() {
      @Override
      public void handleMessage(android.os.Message msg) {
        if (msg.what == 0) {
          invalidate();
          if (mStarted) {
            // 不断发消息给自己,使自己不断被重绘
            mHandler.sendEmptyMessageDelayed(0, 60L);
          }
        }
      }
    };
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = measure(widthMeasureSpec, true);
    int height = measure(heightMeasureSpec, false);
    if (width < height) {
      setMeasuredDimension(width, width);
    } else {
      setMeasuredDimension(height, height);
    }

  }

  /**
   * @category 测量
   * @param measureSpec
   * @param isWidth
   * @return
   */
  private int measure(int measureSpec, boolean isWidth) {
    int result;
    int mode = MeasureSpec.getMode(measureSpec);
    int size = MeasureSpec.getSize(measureSpec);
    int padding = isWidth ? getPaddingLeft() + getPaddingRight()
        : getPaddingTop() + getPaddingBottom();
    if (mode == MeasureSpec.EXACTLY) {
      result = size;
    } else {
      result = isWidth ? getSuggestedMinimumWidth()
          : getSuggestedMinimumHeight();
      result += padding;
      if (mode == MeasureSpec.AT_MOST) {
        if (isWidth) {
          result = Math.max(result, size);
        } else {
          result = Math.min(result, size);
        }
      }
    }
    return result;
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    // TODO Auto-generated method stub
    super.onSizeChanged(w, h, oldw, oldh);
    mScreenWidth = w;
    mScreenHeight = h;
  }

  @Override
  protected void onDraw(Canvas canvas) {
    // TODO Auto-generated method stub
    super.onDraw(canvas);
    // 得到控件的宽高
    int width = getWidth();
    int height = getHeight();
    setBackgroundColor(mContext.getResources().getColor(R.color.main_bg));
    // 计算当前油量线和水平中线的距离
    float centerOffset = Math.abs(mScreenWidth / 2 * mWaterLevel
        - mScreenWidth / 4);
    // 计算油量线和与水平中线的角度
    float horiAngle = (float) (Math.asin(centerOffset / (mScreenWidth / 4)) * 180 / Math.PI);
    // 扇形的起始角度和扫过角度
    float startAngle, sweepAngle;
    if (mWaterLevel > 0.5F) {
      startAngle = 360F - horiAngle;
      sweepAngle = 180F + 2 * horiAngle;
    } else {
      startAngle = horiAngle;
      sweepAngle = 180F - 2 * horiAngle;
    }

    canvas.drawLine(mScreenWidth * 3 / 8, mScreenHeight * 5 / 8,
        mScreenWidth * 5 / 8, mScreenHeight * 5 / 8, linePaint);

    float num = flowPaint.measureText(flowNum);
    canvas.drawText(flowNum, mScreenWidth * 4 / 8 - num / 2,
        mScreenHeight * 4 / 8, flowPaint);
    float left = leftPaint.measureText(flowLeft);
    canvas.drawText(flowLeft, mScreenWidth * 4 / 8 - left / 2,
        mScreenHeight * 3 / 8, leftPaint);

    // 如果未开始(未调用 startWave 方法),绘制一个扇形
    if ((!mStarted) || (mScreenWidth == 0) || (mScreenHeight == 0)) {
      // 绘制,即水面静止时的高度
      RectF oval = new RectF(mScreenWidth / 4, mScreenHeight / 4,
          mScreenWidth * 3 / 4, mScreenHeight * 3 / 4);
      canvas.drawArc(oval, startAngle, sweepAngle, false, mWavePaint);
      return;
    }
    // 绘制,即水面静止时的高度
    // 绘制,即水面静止时的高度
    RectF oval = new RectF(mScreenWidth / 4, mScreenHeight / 4,
        mScreenWidth * 3 / 4, mScreenHeight * 3 / 4);
    canvas.drawArc(oval, startAngle, sweepAngle, false, mWavePaint);

    if (this.c >= 8388607L) {
      this.c = 0L;
    }
    // 每次 onDraw 时 c 都会自增
    c = (1L + c);
    float f1 = mScreenHeight * (1.0F - (0.25F + mWaterLevel / 2))
        - mAmplitude;
    // 当前油量线的长度
    float waveWidth = (float) Math.sqrt(mScreenWidth * mScreenWidth / 16
        - centerOffset * centerOffset);
    // 与圆半径的偏移量
    float offsetWidth = mScreenWidth / 4 - waveWidth;

    int top = (int) (f1 + mAmplitude);
    mPath.reset();
    // 起始振动 X 坐标,结束振动 X 坐标
    int startX, endX;
    if (mWaterLevel > 0.50F) {
      startX = (int) (mScreenWidth / 4 + offsetWidth);
      endX = (int) (mScreenWidth / 2 + mScreenWidth / 4 - offsetWidth);
    } else {
      startX = (int) (mScreenWidth / 4 + offsetWidth - mAmplitude);
      endX = (int) (mScreenWidth / 2 + mScreenWidth / 4 - offsetWidth + mAmplitude);
    }
    // 波浪效果
    while (startX < endX) {
      int startY = (int) (f1 - mAmplitude
          * Math.sin(Math.PI
              * (2.0F * (startX + this.c * width * this.f))
              / width));
      canvas.drawLine(startX, startY, startX, top, mWavePaint);
      startX++;
    }
    canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2, mScreenWidth / 4
        + mRingSTROKEWidth / 2, mRingPaint);

    canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2,
        mScreenWidth / 4, mCirclePaint);
    canvas.restore();
  }

  @Override
  public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    SavedState ss = new SavedState(superState);
    ss.progress = (int) c;
    return ss;
  }

  @Override
  public void onRestoreInstanceState(Parcelable state) {
    SavedState ss = (SavedState) state;
    super.onRestoreInstanceState(ss.getSuperState());
    c = ss.progress;
  }

  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    // 关闭硬件加速,防止异常 unsupported operation exception
    this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
  }

  /**
   * @category 开始波动
   */
  public void startWave() {
    if (!mStarted) {
      this.c = 0L;
      mStarted = true;
      this.mHandler.sendEmptyMessage(0);
    }
  }

  /**
   * @category 停止波动
   */
  public void stopWave() {
    if (mStarted) {
      this.c = 0L;
      mStarted = false;
      this.mHandler.removeMessages(0);
    }
  }

  /**
   * @category 保存状态
   */
  static class SavedState extends BaseSavedState {
    int progress;

    /**
     * Constructor called from {@link ProgressBar#onSaveInstanceState()}
     */
    SavedState(Parcelable superState) {
      super(superState);
    }

    /**
     * Constructor called from {@link #CREATOR}
     */
    private SavedState(Parcel in) {
      super(in);
      progress = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
      super.writeToParcel(out, flags);
      out.writeInt(progress);
    }

    public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
      public SavedState createFromParcel(Parcel in) {
        return new SavedState(in);
      }

      public SavedState[] newArray(int size) {
        return new SavedState[size];
      }
    };
  }

}

我们运行一下

其实他是十分的空旷的,所以也值得我们去定制,我们在中间加个流量显示,再加个进度条

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/main_bg" >

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="10dp"
    android:text="流量"
    android:textColor="@android:color/white"
    android:textSize="18sp" />

  <com.lgl.circleview.CircleView
    android:id="@+id/wave_view"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_centerInParent="true" />

  <TextView
    android:id="@+id/power"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:textColor="@android:color/white" />

  <SeekBar
    android:id="@+id/seekBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_marginBottom="150dp" />

</RelativeLayout>

我们要实现这个,就要调用它的初始化以及 start 方法

  mCircleView = (CircleView) findViewById(R.id.wave_view);
    // 设置多高,float,0.1-1F
    mCircleView.setmWaterLevel(0.1F);
    // 开始执行
    mCircleView.startWave();

别忘了 activity 销毁的时候把它回收哦

@Override
  protected void onDestroy() {
    // TODO Auto-generated method stub
    mCircleView.stopWave();
    mCircleView = null;
    super.onDestroy();
  }

我们再运行一遍

但是我们要怎么让水波纹随着进度条一起上升下降尼?,这里我们就要用到我们刚才写的 SeekBar 了,我们实现它的 setOnSeekBarChangeListener 来监听,这样我们就要复写他的三个方法,这里我们只要用到一个

public void onProgressChanged(SeekBar seekBar, int progress,
          boolean fromUser) {
        //跟随进度条滚动
        mCircleView.setmWaterLevel((float) progress / 100);
        }

这里,我们要这样算的,我们设置高度的单位是 float,也就是从 0-1F,而我们的进度是 int progress,从 0-100,我们就要用(float) progress / 100) 并且强转来得到单位,好了,我们现在水波纹的高度就是随着我们的进度条一起变化了,我们再来运行一下

好的,这样的话,我们就只剩下一个了,就是让大小随着我们的进度条变化了,这里我们因为更新 UI 不能再主线程中操作,所以我们需要用到我们的老伙计 Handler 了,但是用到 handler 还不够,我们的进度条数值也是在内部类里面,所以这里我们需要用到 Handler 来传值了,这里我们用的是 Bundle,我们还是在 onProgressChanged 方法中操作了

        //创建一个消息
        Message message = new Message();
        Bundle bundle = new Bundle();
        //put 一个 int 值
        bundle.putInt("progress", progress);
        //装载
        message.setData(bundle);
        //发送消息
        handler.sendMessage(message);
        //创建表示
        message.what = 1;

消息发送过去了,我们就在前面写个 Handler 去接收就是了

  private Handler handler = new Handler() {
    public void handleMessage(android.os.Message msg) {
      if (msg.what == 1) {
        int num = msg.getData().getInt("progress");
        Log.i("num", num + "");
        power.setText((float) num / 100 * max + "M/" + max + "M");
      }
    }
  };

这里的计算公式尼,是当前的数值/100 得到百分比再去*最大值。我们现在可以完整的运行一下了,其实和最上面运行的图片是一样的

MainActivity

package com.lgl.circleview;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.SeekBar;
import android.widget.TextView;

public class MainActivity extends Activity {

  private CircleView mCircleView;
  private SeekBar mSeekBar;
  private TextView power;
  private int max = 1024;
  private int min = 102;

  private Handler handler = new Handler() {
    public void handleMessage(android.os.Message msg) {
      if (msg.what == 1) {
        int num = msg.getData().getInt("progress");
        Log.i("num", num + "");
        power.setText((float) num / 100 * max + "M/" + max + "M");
      }
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getActionBar().hide();
    setContentView(R.layout.activity_main);

    power = (TextView) findViewById(R.id.power);
    power.setText(min + "M/" + max + "M");

    mCircleView = (CircleView) findViewById(R.id.wave_view);
    // 设置多高,float,0.1-1F
    mCircleView.setmWaterLevel(0.1F);
    // 开始执行
    mCircleView.startWave();

    mSeekBar = (SeekBar) findViewById(R.id.seekBar);
    mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
      @Override
      public void onProgressChanged(SeekBar seekBar, int progress,
          boolean fromUser) {
        mCircleView.setmWaterLevel((float) progress / 100);
        // 创建一个消息
        Message message = new Message();
        Bundle bundle = new Bundle();
        // put 一个 int 值
        bundle.putInt("progress", progress);
        // 装载
        message.setData(bundle);
        // 发送消息
        handler.sendMessage(message);
        // 创建表示
        message.what = 1;
      }

      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {

      }

      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {

      }
    });
  }

  @Override
  protected void onDestroy() {
    // TODO Auto-generated method stub
    mCircleView.stopWave();
    mCircleView = null;
    super.onDestroy();
  }
}

Demo 下载地址: http://download.csdn.net/detail/qq_26787115/9435934

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文