Android 分享一个流量显示界面

版本:1.0

日期:2014.8.9 2014.9.24

版权:© 2014 kince 转载注明出处

波形效果有几种不同的呈现形式,比如从中间向四周散开的波形,也就是熟知的水涟漪;还有上下波动的曲线,像五线谱等。英文中可以称作Wave或者Ripple,所以暂且叫它们WaveView、WaveLayout、RippleView、RippleLayout,接下来开始实现这些效果。

首先看一下Solo 火爆足球动态壁纸,

下面中间的按钮就是一个波形按钮,它会不断地向四周辐射,由于是静态图,如果想体验真实效果可以另行下载。这种效果的实现思路是不断绘制圆形,当然半径也要不断变化,透明度也是一样。代码如下:

/**
*
*/
package com.kince.rippleview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;

/**
* @author kince
* @category 波纹
* @since 2014.8.9
* @version v1.0.0
*
*/
public class RippleView extends View {

     private int mScreenWidth;
     private int mScreenHeight;

     private Bitmap mRippleBitmap;
     private Paint mRipplePaint = new Paint();

     private int mBitmapWidth;
     private int mBitmapHeight;

     private boolean isStartRipple;

     private int heightPaddingTop;
     private int heightPaddingBottom;
     private int widthPaddingLeft;
     private int widthPaddingRight;

     private RectF mRect = new RectF();

     private int rippleFirstRadius = 0;
     private int rippleSecendRadius = -33;
     private int rippleThirdRadius = -66;

     private Paint textPaint = new Paint();
    private String mText="点击我吧";

     private Handler handler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
               // TODO Auto-generated method stub
               super.handleMessage(msg);
               invalidate();
               if (isStartRipple) {
                    rippleFirstRadius++;
                    if (rippleFirstRadius > 100) {
                         rippleFirstRadius = 0;
                    }
                    rippleSecendRadius++;
                    if (rippleSecendRadius > 100) {
                         rippleSecendRadius = 0;
                    }
                    rippleThirdRadius++;
                    if (rippleThirdRadius > 100) {
                         rippleThirdRadius = 0;
                    }
                    sendEmptyMessageDelayed(0, 20);
               }
          }
     };

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

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

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

     private void init() {
          mRipplePaint.setColor(4961729);
          mRipplePaint.setAntiAlias(true);
          mRipplePaint.setStyle(Paint.Style.FILL);

          textPaint.setTextSize(26);
          textPaint.setAntiAlias(true);
          textPaint.setStyle(Paint.Style.FILL);
          textPaint.setColor(Color.WHITE);

          mRippleBitmap = BitmapFactory.decodeStream(getResources()
                    .openRawResource(R.drawable.easy3d_ic_apply));
          mBitmapWidth = mRippleBitmap.getWidth();
          mBitmapHeight = mRippleBitmap.getHeight();
     }

     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          // TODO Auto-generated method stub
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          int mh = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
          int mw = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
          if (mBitmapWidth < 2 * mBitmapHeight) {
               mBitmapWidth = (2 * mBitmapHeight);
          }
          setMeasuredDimension(mBitmapWidth, mBitmapHeight);
     }

     @Override
     protected void onDraw(Canvas canvas) {
          // TODO Auto-generated method stub
          super.onDraw(canvas);
          if (isStartRipple) {
               float f1 = 3 * mBitmapHeight / 10;
               mRipplePaint.setAlpha(255);
               canvas.drawCircle(mBitmapWidth / 2, mBitmapHeight,
                         7 * mBitmapHeight / 10, mRipplePaint);
               int i1 = (int) (220.0F - (220.0F - 0.0F) / 100.0F
                         * rippleFirstRadius);
               mRipplePaint.setAlpha(i1);
               canvas.drawCircle(mBitmapWidth / 2, mBitmapHeight, 7
                         * mBitmapHeight / 10 + f1 * rippleFirstRadius / 100.0F,
                         mRipplePaint);
               if (rippleSecendRadius >= 0) {
                    int i3 = (int) (220.0F - (220.0F - 0.0F) / 100.0F
                              * rippleSecendRadius);
                    mRipplePaint.setAlpha(i3);
                    canvas.drawCircle(mBitmapWidth / 2, mBitmapHeight,
                              7 * mBitmapHeight / 10 + f1 * rippleSecendRadius
                                        / 100.0F, mRipplePaint);
               }
               if (rippleThirdRadius >= 0) {
                    int i2 = (int) (220.0F - (220.0F - 0.0F) / 100.0F
                              * rippleThirdRadius);
                    mRipplePaint.setAlpha(i2);
                    canvas.drawCircle(mBitmapWidth / 2, mBitmapHeight, 7
                              * mBitmapHeight / 10 + f1 * rippleThirdRadius / 100.0F,
                              mRipplePaint);
               }

          }
          mRipplePaint.setAlpha(30);
          canvas.drawCircle(mBitmapWidth / 2, mBitmapHeight, mBitmapHeight,
                    mRipplePaint);
          mRipplePaint.setAlpha(120);
          canvas.drawCircle(mBitmapWidth / 2, mBitmapHeight,
                    9 * mBitmapHeight / 10, mRipplePaint);
          mRipplePaint.setAlpha(180);
          canvas.drawCircle(mBitmapWidth / 2, mBitmapHeight,
                    8 * mBitmapHeight / 10, mRipplePaint);
          mRipplePaint.setAlpha(255);
          canvas.drawCircle(mBitmapWidth / 2, mBitmapHeight,
                    7 * mBitmapHeight / 10, mRipplePaint);
          float length = textPaint.measureText(mText);
          canvas.drawText(mText, (mBitmapWidth - length) / 2,
                    mBitmapHeight * 3 / 4, textPaint);

     }

     @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;
          confirmSize();
          invalidate();

     }

     private void confirmSize() {
          int minScreenSize = Math.min(mScreenWidth, mScreenHeight);
          int widthOverSize = mScreenWidth - minScreenSize;
          int heightOverSize = mScreenHeight - minScreenSize;
          heightPaddingTop = (getPaddingTop() + heightOverSize / 2);
          heightPaddingBottom = (getPaddingBottom() + heightOverSize / 2);
          widthPaddingLeft = (getPaddingLeft() + widthOverSize / 2);
          widthPaddingRight = (getPaddingRight() + widthOverSize / 2);

          int width = getWidth();
          int height = getHeight();

          mRect = new RectF(widthPaddingLeft, heightPaddingTop, width
                    - widthPaddingRight, height * 2 - heightPaddingBottom);

     }

     public void stratRipple() {
          isStartRipple = true;
          handler.sendEmptyMessage(0);
     }

}

下图是某个应用的流量显示界面,使用的是上面说的第二种形式。

实现上面效果的思路是使用正弦或者余弦曲线,代码如下:

/**
*
*/
package com.kince.waveview;

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 kince
* @category View必须是正方形
*
*/
public class WaterWaveView 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 mWateLevel = 0.5F;// 水高(0~1)
     private Path mPath;

     private String flowNum = "1024M";
     private String flowLeft = "还剩余";

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

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

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

     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(18);

          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.holo_purple2));

          canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2,
                    mScreenWidth / 4, mRingPaint);

          canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2, mScreenWidth / 4
                    - mRingSTROKEWidth / 2, mCirclePaint);
          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 + mRingSTROKEWidth / 2,
                         mScreenHeight / 4 + mRingSTROKEWidth / 2, mScreenWidth * 3
                                   / 4 - mRingSTROKEWidth / 2, mScreenHeight * 3 / 4
                                   - mRingSTROKEWidth / 2);// 设置个新的长方形,扫描测量
               canvas.drawArc(oval, 0, 180, true, mWavePaint);
               return;
          }
          // 绘制,即水面静止时的高度
          RectF oval = new RectF(mScreenWidth / 4 + mRingSTROKEWidth / 2,
                    mScreenHeight / 4 + mRingSTROKEWidth / 2 + mAmplitude * 2,
                    mScreenWidth * 3 / 4 - mRingSTROKEWidth / 2, mScreenHeight * 3
                              / 4 - mRingSTROKEWidth / 2);// 设置个新的长方形,扫描测量
          canvas.drawArc(oval, 0, 180, true, mWavePaint);

          if (this.c >= 8388607L) {
               this.c = 0L;
          }
          // 每次onDraw时c都会自增
          c = (1L + c);
          float f1 = mScreenHeight * (1.0F - mWateLevel);
          int top = (int) (f1 + mAmplitude);
          mPath.reset();
          int startX = mScreenWidth / 2 - mScreenWidth / 4 + mRingSTROKEWidth / 2;
          // 波浪效果
          while (startX < mScreenWidth / 2 + mScreenWidth / 4 - mRingSTROKEWidth
                    / 2) {
               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.restore();
     }

     @Override
     public Parcelable onSaveInstanceState() {
          // Force our ancestor class to save its state
          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];
               }
          };
     }

}

github下载地址:

https://github.com/wangjinyu501/RippleView

https://github.com/wangjinyu501/WaterWaveView/

时间: 2024-10-11 06:04:51

Android 分享一个流量显示界面的相关文章

android 分享一个处理BaseAdapter,getView()多次加载的方法

一:BaseAdapter介绍 BaseAdapter是listview,gridview等列表,使用的数据适配器,它的主要用途是将一组数据传到ListView.Spinner.Gallery及GridView等UI显示组件,如果listView列表的数据项过多,如1000项,我们如果把这1000项全部放到界面中去,软件直接内存溢出了,BaseAdapter刚才可以帮我们解决这个问题,BaseAdapter工作原理图如下: 从上图中看出,如果我们有1000个数据项,实际显示的只有7项,其它的缓存

分享一个漂亮WPF界面框架创作过程及其源码

本文会作为一个系列,分为以下部分来介绍: (1)见识一下这个界面框架: (2)界面框架如何进行开发: (3)辅助开发支持:Demo.模板.VsPackage制作. 框架源码如下所示. 本文介绍第(1)部分. 1 安装 现在我们就先来见识一下这个界面框架.首先,你可以通过以下链接来下载到这个框架的VS插件安装包:下载地址.下载解压后,文件如下: 双击这个文件,进行安装(目前只支持VS2012和VS2013,抛弃了VS2010,I am sorry). 点击安装,即可完成. 2 创建主程序 接着打开

android:分享 一个非常强大的LOG开关---Log.isLoggable

1.API亮点: 此API能够实现不更换APK.在出问题的手机上就直接能抓到有效log,能提升不少工作效率. 2.API介绍 近期在解决短信问题时.看到一个非常强大的LOG开关---Log.isLoggable 1. if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 2.     Log.v(TAG, "Creating TransactionService"); 3. } 进入framework中查看isLoggable方法

Android 分享一个SharedPreferences的工具类,方便保存数据

我们平常保存一些数据,都会用到SharedPreferences,他是保存在手机里面的,具体路径是data/data/你的包名/shared_prefs/保存的文件名.xml, SharedPreferences的使用也很简单,我自己就写了一个SharedPreferences的工具类,然后就保存在这里,等自己以后需要保存数据直接从这里copy代码,哈哈 工具类如下 [java] view plaincopy package com.example.shortcut; import androi

android:分享 一个很强大的LOG开关---Log.isLoggable

1.API亮点: 此API可以实现不更换APK,在出问题的手机上就直接能抓到有效log,能提升不少工作效率. 2.API介绍 最近在解决短信问题时,看到一个很强大的LOG开关---Log.isLoggable 1. if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 2.     Log.v(TAG, "Creating TransactionService"); 3. } 进入framework中查看isLoggable方法的

Android 分享一个简单有趣的动画效果

本期开始于大家分享几个简单又有趣的动画 效果图: 效果还是不错的,而实现起来也并没有多大的难度,上代码喽 public int id[] = { R.drawable.fengjing_1, R.drawable.fengjing_2, R.drawable.fengjing_3, R.drawable.fengjing_4, R.drawable.fengjing_5, R.drawable.fengjing_6 }; public String list[] = { "一", &q

【源码分享】WPF漂亮界面框架实现原理分析及源码分享

1 源码下载 2 OSGi.NET插件应用架构概述 3 漂亮界面框架原理概述 4 漂亮界面框架实现  4.1 主程序  4.2 主程序与插件的通讯   4.2.1 主程序获取插件注册的服务   4.2.2 插件获取主程序注册的服务   4.2.3 服务接口  4.3 权限管理插件的登录窗体  4.4 界面框架插件   4.4.1 导航服务   4.4.2 界面框架扩展实现  4.5 插件   4.5.1 插件引用了第三方程序集   4.5.2 一个程序集如何让所有插件都直接使用   4.5.3

分享一个搭建流量频道的经历

最近接触了一个很小众的行业,行业本身的搜索流量很小.朋友来问我SEO该怎么做. 于是有了这次经历,目前各项数据都还在测试观察中.好了开始. 仔细研究了一下他所在的行业,发现虽然是一个新兴的小众的行业,但是能跟娱乐类的信息扯上关系.于是准备搭建一个明星资料库来引入一批流量.由于这个朋友是技术出身,技术也比较牛,于是几乎把想到的东西都实现了. 首先从词库入手,既然是搭建明星资料库,那么从获取明星名字开始,这个从很多软件或者网站上都能够直接跑出来!经过对部分歌手的抽样分析,发现稍微热门一点的歌手,基本

android listView 的最后一个item显示(菜鸟新手,老鸟勿喷)

问题:一个listView显示不满全屏时,最后一个item会自动占满剩下的屏幕高度,而我们需要看到的是每个item布局所要占的高度一样. 解决办法:主要是把ListView布局里面的高度设置为:android:layout_height="fill_parent" : listView显示的每个item就占同样的高度了.