版本: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下载地址: