Android自定义WaveView实现波浪进度效果

实现原理

首先就是自定义个WaveView 继承View,然后再WaveView 内部实现代码逻辑:

① 水波就波嘛? sin函数? 贝塞尔曲线? 都行,这里就用二阶贝塞 尔曲线去画吧

② 波要动嘛,怎么动呢?线程? 好吧 这里用了个Handler。

③绘制波首先要找点,那么在onMeasure()里找出需要的点咯,这里就暂时展示一个波段吧,一个波长移动左边不就没了?OK 那就两个波吧,吼吼,两个波(猥琐男潜质表露无遗啊)。接下来就是Handler 结合 onDraw()绘制。OK,那就先看我Word绘制的粗瘪的波动图,请看VCR,oh,no... gif

意思就是波平移一个波长之后回到初始位置继续平移循环。

好吧,有人说了,这么简单的逻辑你要啰嗦那么多???

好吧,我承认,我有唐僧的潜质。。。

闲话就不说了,先上

效果图

示例代码如下

调用的Activity

 * Created by LiuDong on 2016/12/22.
 * Email:15002102128@126.com
 */

public class WaveActivity extends Activity {
 LD_WaveView waveView;//方形
 LD_WaveView waveCircleView;//圆形
 private int progrees=0;//进度
 private Handler mHandler=new Handler(){
  @Override
  public void handleMessage(Message msg) {
   if (progrees==100) progrees=0;
   Log.i("progress",progrees+"");
   waveView.setmProgress(progrees++);
   waveCircleView.setmProgress(progrees++);
   mHandler.sendEmptyMessageDelayed(0,100);
  }
 };
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_wave);
  waveView= (LD_WaveView) findViewById(R.id.waveView);
  waveCircleView= (LD_WaveView) findViewById(R.id.waveViewCircle);
  mHandler.sendEmptyMessageDelayed(0,10);
 }
}

xml布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:orientation="vertical" android:layout_width="match_parent"
 android:background="@color/ld_White"
 android:layout_height="match_parent">
 <com.dadong.ld_tools.widget.LD_WaveView
  android:id="@+id/waveViewCircle"
  android:layout_marginTop="20dp"
  android:layout_width="100dp"
  android:layout_centerHorizontal="true"
  android:layout_height="100dp"
  app:wave_color="@color/ld_Black"
  app:wave_circle="true"
  />
 <com.dadong.ld_tools.widget.LD_WaveView
  android:id="@+id/waveView"
  android:layout_width="100dp"
  android:layout_height="100dp"
  app:wave_color="@color/ld_Black"
  app:wave_circle="false"
  android:layout_centerInParent="true" />
</RelativeLayout>

自定义WaveView

/**
 * Created by LiuDong on 2016/12/23.
 * Email:15002102128@126.com
 */

public class LD_WaveView extends View {

 private int mProgress;//进度
 private int mTimeStep = 10;//时间间隔
 private int mSpeed = 5;//波单次移动的距离
 private int mViewHeight;//视图宽高
 private int mViewWidth;//视图宽度
 private int mLevelLine;// 基准线

 private int mWaveLength;//波长 暂定view宽度为一个波长
 private int mStrokeWidth;//园的线宽
 private RectF rectF;//圆环区域
 private int mWaveHeight;//波峰高度
 private int mLeftWaveMoveLength;//波平移的距离,用来控制波的起点位置
 private int mWaveColor;//波的颜色
 private Paint mPaint;//画笔
 private Paint mCirclePaint;//圆环画笔
 private Paint mBorderPaint;//边界画笔
 private int mBorderWidth=4;//边界宽度
 private Paint mTextPaint;//文字画笔
 private Path mPath;//绘画线
 private List<Point> mPoints;//点的集合
 private boolean isMeasure = false;//是否已测量过
 private boolean isCircle=false;//是否圆形默认false,可属性代码设置
 //处理消息
 private Handler handler = new Handler() {
  @Override
  public void handleMessage(Message msg) {

   initWaveMove();
  }
 };

 /**
  * 初始化波的移动
  */
 private void initWaveMove(){
  mLeftWaveMoveLength+=mSpeed;//波向右移动距离增加mSpeed;
  if (mLeftWaveMoveLength>=mWaveLength){//当增加到一个波长时回复到0
   mLeftWaveMoveLength=0;
  }
  invalidate();

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

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

 public LD_WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);

  getAttr(context, attrs, defStyleAttr);
  init();

 }

 /**
  * 初始化画笔
  */
 private void init() {
  mPoints = new ArrayList<Point>();
  //波浪轨迹画笔
  mPaint = new Paint();
  mPaint.setAntiAlias(true);
  mPaint.setColor(mWaveColor);
  mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

  mPath = new Path();

  //文字画笔
  mTextPaint=new Paint();
  mTextPaint.setColor(Color.RED);
  mTextPaint.setTextAlign(Paint.Align.CENTER);
  mTextPaint.setTextSize(48);

  //圆环画笔
  mCirclePaint=new Paint();
  mCirclePaint.setAntiAlias(true);
  mCirclePaint.setColor(Color.WHITE);
  mCirclePaint.setStyle(Paint.Style.STROKE);
  //边界线画笔
  mBorderPaint=new Paint();
  mBorderPaint.setAntiAlias(true);
  mBorderPaint.setColor(mWaveColor);
  mBorderPaint.setStrokeWidth(mBorderWidth);
  mBorderPaint.setStyle(Paint.Style.STROKE);

 }

 /**
  * 获取自定义的属性值
  *
  * @param attrs
  */
 private void getAttr(Context context, AttributeSet attrs, int defStyle) {

  TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LD_WaveView, defStyle, 0);

  mWaveColor = a.getColor(R.styleable.LD_WaveView_wave_color, Color.RED);
  isCircle=a.getBoolean(R.styleable.LD_WaveView_wave_circle,false);
  a.recycle();

 }

 /**
  *
  * @param widthMeasureSpec
  * @param heightMeasureSpec
  */
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  if (!isMeasure&&Math.abs(getMeasuredHeight()-getMeasuredWidth())<50) {//只计算一次就够了 ,relativelayout的时候要绘制两次 加个宽高判断
   mViewHeight = getMeasuredHeight();
   mViewWidth = getMeasuredWidth();
   mLevelLine = mViewHeight; //初始化波的准位线  起始位视图最底部
   {
    mLevelLine = mViewHeight * (100-mProgress) / 100;
    if (mLevelLine < 0) mLevelLine = 0;
   }
   //计算波峰值
   mWaveHeight = mViewHeight / 20;//波峰暂定为view高度的1/20,如果需要设置 可设置set方法赋值;
   mWaveLength = getMeasuredWidth();

   //计算所有的点 这里取宽度为整个波长 往左再延伸一个波长 两个波长则需要9个点
   for (int i = 0; i < 9; i++) {
    int y = 0;
    switch (i % 4) {
     case 0:
      y = mViewHeight;
      break;
     case 1:
      y =mViewHeight+ mWaveHeight;
      break;
     case 2:
      y = mViewHeight;
      break;
     case 3:
      y = mViewHeight-mWaveHeight;
      break;
    }
    Point point = new Point(-mWaveLength + i * mWaveLength / 4, y);
    mPoints.add(point);
   }
   /**
    * 计算圆环宽度
    */
   int mIncircleRadius=mViewHeight<mViewWidth?mViewHeight/2:mViewWidth/2;//内切圆半径

   int mcircumcircleRadius= (int) (Math.sqrt((float)(Math.pow(mViewHeight/2,2)+Math.pow(mViewWidth/2,2)))+0.5);//外接圆半径
   int radius=mcircumcircleRadius/2+mIncircleRadius/2;

   rectF=new RectF(mViewWidth/2-radius,mViewHeight/2-radius,mViewWidth/2+radius,mViewHeight/2+radius);
   mStrokeWidth=mcircumcircleRadius-mIncircleRadius;
   mCirclePaint.setStrokeWidth(mStrokeWidth);//线是有宽度的 采用了这种方式画圆环
   isMeasure = true;
  }
 }

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  /**
   * 绘制线条
   */
  mPath.reset();
  int i = 0;
  mPath.moveTo(mPoints.get(0).getX()+mLeftWaveMoveLength, mPoints.get(0).getY()-mViewHeight*mProgress/100);
  for (; i < mPoints.size() - 2; i += 2) {
   mPath.quadTo(mPoints.get(i + 1).getX()+mLeftWaveMoveLength, mPoints.get(i + 1).getY()-mViewHeight*mProgress/100, mPoints.get(i + 2).getX()+mLeftWaveMoveLength, mPoints.get(i + 2).getY()-mViewHeight*mProgress/100);
  }
  mPath.lineTo(mPoints.get(i).getX()+mLeftWaveMoveLength, mViewHeight);
  mPath.lineTo(mPoints.get(0).getX()+mLeftWaveMoveLength, mViewHeight);
  mPath.close();
  /**
   * 绘制轨迹
   */
  canvas.drawPath(mPath,mPaint);
  Rect rect = new Rect();

  String progress=String.format("%d%%",mProgress);
  mTextPaint.getTextBounds(progress,0,progress.length(), rect);
  int textHeight = rect.height();
  if (mProgress>=50)//如果进度达到50 颜色变为白色,没办法啊,进度在中间 不变颜色看不到
   mTextPaint.setColor(Color.WHITE);
  else
  mTextPaint.setColor(mWaveColor);
  canvas.drawText(progress,0,progress.length(),mViewWidth/2,mViewHeight/2+textHeight/2,mTextPaint);

  if (isCircle) {
   /**
    * 绘制圆环
    */

   canvas.drawArc(rectF, 0, 360, true, mCirclePaint);
   Paint circlePaint = new Paint();
   circlePaint.setStrokeWidth(5);
   circlePaint.setColor(Color.WHITE);
   circlePaint.setAntiAlias(true);
   circlePaint.setStyle(Paint.Style.STROKE);
   canvas.drawCircle(mViewWidth / 2, mViewHeight / 2, mViewHeight / 2, circlePaint);
   /**
    * 绘制边界
    */

   mBorderPaint.setStrokeWidth(mBorderWidth/2);
  canvas.drawCircle(mViewWidth/2,mViewHeight/2,mViewHeight/2-mBorderWidth/2,mBorderPaint);
  }else {
   /**
    * 绘制矩形边框
    */
   canvas.drawRect(0,0,mViewWidth,mViewHeight,mBorderPaint);
  }
  //
  handler.sendEmptyMessageDelayed(0,mTimeStep);
 }

 /**
  * 设置进度 基准线
  * @param mProgress
  */
 public void setmProgress(int mProgress) {
  this.mProgress = mProgress;
  mLevelLine=(100-mProgress)*mViewHeight/100;
 }

 /**
  * 设置是否为圆形
  * @param circle
  */
 public void setCircle(boolean circle) {
  isCircle = circle;
 }
}

自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="LD_WaveView">
  <attr name="wave_color" format="color"></attr>
  <attr name="wave_circle" format="boolean"></attr>
 </declare-styleable>
</resources>

总结

好了,以上就是这篇文章的全部内容了,代码里备注应该还算比较清楚了,希望能对一些人有一些帮助,瑕疵不足之处欢迎指正,或者有好的建议。也可以留言交流。

(0)

相关推荐

  • android自定义ImageView仿图片上传示例

    看下效果图 主要看下自定义view 代码 public class ProcessImageView extends ImageView{ private Context context; private Paint paint; private LogUtil log=LogUtil.getInstance(); int progress = 0; private boolean flag; public ProcessImageView(Context context) { super(co

  • Android自定义View之边框文字、闪烁发光文字

    对现有控件进行扩展 1.绘制如下所示的两层背景的TextView 创建BorderTextView继承TextView 在构造函数中初始化一些基本数据 //外边框 mPaint1 = new Paint(); mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_bright)); //画笔的样式,充满 mPaint1.setStyle(Paint.Style.FILL); //内边框 mPaint2 = new P

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

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

  • Android自定义view之仿支付宝芝麻信用仪表盘示例

    自定义view练习 仿支付宝芝麻信用的仪表盘 对比图: 首先是自定义一些属性,可自己再添加,挺基础的,上代码 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="RoundIndicatorView"> <!--最大数值--> <attr name="maxNum" form

  • Android自定义View 实现水波纹动画引导效果

    一.实现效果图 二.实现代码 1.自定义view package com.czhappy.showintroduce.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Pat

  • Android编程使用自定义View实现水波进度效果示例

    本文实例讲述了Android编程使用自定义View实现水波进度效果.分享给大家供大家参考,具体如下: 首先上效果图: 简介: 1.自动适应屏幕大小: 2.水波自动横向滚动: 3.各种绘制参数可通过修改常量进行控制. 代码不多,注释也比较详细,全部贴上: (一)自定义组件: /** * 水波进度效果. */ public class WaterWaveView extends View { //边框宽度 private int STROKE_WIDTH; //组件的宽,高 private int

  • Android 自定义view仿支付宝咻一咻功能

    支付宝上有一个咻一咻的功能,就是点击图片后四周有水波纹的这种效果,今天也写一个类似的功能. 效果如下所示: 思路: 就是几个圆的半径不断在变大,这个可以使用动画缩放实现,还有透明动画 还有就是这是好几个圆,然后执行的动画有个延迟效果,其实这些动画是放在一起执行的,熟悉属性动画的知道已经给我们提供了同步执行动画和顺序执行动画的实现api,也会会有人说这几个view就是在onDraw()方法中画几个圆,可能会说我还要继承容器view去onLayout()方法中这些子view添加在某个特定的区域,当然

  • Android自定义View实现闪耀字体效果

    本文实例为大家分享了闪耀字体效果的具体代码,供大家参考,具体内容如下 import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.gr

  • Android自定义View实现通讯录字母索引(仿微信通讯录)

    一.效果:我们看到很多软件的通讯录在右侧都有一个字母索引功能,像微信,小米通讯录,QQ,还有美团选择地区等等.这里我截了一张美团选择城市的图片来看看: 我们今天就来实现图片中右侧模块的索引功能,包括触摸显示以选中的索引字母.这里我的UI界面主要是参照微信的界面来实现,所以各位也可以对照微信来看看效果,什么都不说了,只有效果图最具有说服力! 二.分析: 我们看到这样的效果我们心理都回去琢磨,他是如何实现的: 首先,它肯定是通过自定义 View 来实现的,因为 Android 没有提供类似这样的控件

  • Android自定义Animation实现View摇摆效果

    使用自定义Animation,实现View的左右摇摆效果,如图所示: 代码很简单,直接上源码 activity_maini.xml布局文件: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_

  • Android自定义View实现shape图形绘制

    概述 之前曾写过一篇文章介绍了Android中drawable使用Shape资源,通过定义drawable中的shape资源能够绘制简单的图形效果,如矩形,椭圆形,线形和圆环等.后来我在项目中正好遇到这样一个需求,要在特定的位置上显示一条垂直的虚线.正当我胸有成竹的把上面的资源文件放入进去的时候,我才发现它并不能符合我的要求.使用shape画出的垂直虚线,其实就是将一条水平的线,旋转90度.但这样做的弊端就是,该View有效区域为旋转90度后与原来位置相重合的区域,还不能随意的改动,这样的效果根

  • Android自定义View实现仿GitHub的提交活跃表格

    说明 本文可能需要一些基础知识点,如Canvas,Paint,Path,Rect等类的基本使用,建议不熟悉的同学可以学习GcsSloop安卓自定义View教程目录,会帮助很大. 上图就是github的提交表格,直观来看可以分为几个部分进行绘制: (1)各个月份的小方格子,并且色彩根据提交次数变化,由浅到深 (2)右下边的颜色标志,我们右对齐就可以了 (3)左边的星期,原图是从周日画到周六,我们从周一画到周日 (4)上面的月份,我们只画出1-12月 (5)点击时候弹出当天的提交情况,由一个小三角和

随机推荐