Android 气泡上升效果

声明:


根据原文
http://blog.csdn.net/u014608640/article/details/52487653
基础上修改, 模拟碰撞, 可随机颜色, 气泡模糊等


成员变量定义

private static final int MSG_CREATE_BUBBLE = 0;
public static final int MAX_LEVEL = 100;
public static final int MIN_LEVEL = 0;
protected float mWidth;
protected float mHeight;
//底部填充色
protected int mColor = 0xFF61d66b;
//气泡上升高度(水面高度)
protected float mTop;
//底部 == mHeight
protected float mBottom;
protected Paint mPaint;
// 0 - 100 mTop根据此值计算
protected int mLevel = 100;
//存放气泡集合
private List mBubbles = new CopyOnWriteArrayList<>();
private Random mRandom = new Random();
private boolean mStarting = false;
private Paint mBubblePaint;
//气泡随机颜色值
private String[] mBubbleColors = {
          "#ffec5c",
          "#00ccff",
          "#ffffff"
  };

获取控件宽高

mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);

onDraw 所有气泡都在这里面绘制

@Override
protected final void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.save();
    //计算top
    mTop = mHeight - (int) (mHeight * (mLevel *1f / MAX_LEVEL));
    mBottom = mHeight;

    //颜色填充
    //top
    RectF topRectF = new RectF(0, 0, mWidth, mTop);
    mPaint.setAlpha(50);
    canvas.drawRect(topRectF, mPaint);
    //bottom
    RectF bottomRectF = new RectF(0, mTop, mWidth, mBottom);
    mPaint.setAlpha(255);
    canvas.drawRect(bottomRectF, mPaint);
    //画气泡
    drawBubble(canvas);
    invalidate();
}

接下来就要气泡的产生过程及绘制

气泡实体类

private class Bubble {
   /** 气泡半径 */
   private float radius;
   /** 上升速度 */
   private float speedY;
   /** 平移速度 */
   private float speedX;
   /** 气泡x坐标 */
   private float x;
   /** 气泡y坐标 */
   private float y;
   /** 距离顶部消失距离 */
   private float removeY;
   /** 渐变色 */
   private int[] shaderColor;
   /* 原使色 */
   private int originalColor;

   public float getRadius() {
       return radius;
   }

   public void setRadius(float radius) {
       this.radius = radius;
   }

   public float getX() {
       return x;
   }

   public void setX(float x) {
       this.x = x;
   }

   public float getY() {
       return y;
   }

   public void setRemoveY(float y) {
       this.removeY = y;
   }
   public float getRemoveY() {
       return removeY;
   }

   public void setY(float y) {
       this.y = y;
   }

   public float getSpeedY() {
       return speedY;
   }

   public void setSpeedY(float speedY) {
       this.speedY = speedY;
   }

   public float getSpeedX() {
       return speedX;
   }

   public void setSpeedX(float speedX) {
       this.speedX = speedX;
   }

   public void setShaderColor(int color) {
       originalColor = color;
       this.shaderColor = new int[] {
               ColorUtil.parseColor(color, "#cc"),
               ColorUtil.parseColor(color, "#cc"),
               ColorUtil.parseColor(color, "#cc"),
               ColorUtil.parseColor(color, "#aa"),
               ColorUtil.parseColor(color, "#99"),
               ColorUtil.parseColor(color, "#77"),
               ColorUtil.parseColor(color, "#44"),
               ColorUtil.parseColor(color, "#00"),
       };
   }

   public int[] getShaderColor() {
       return this.shaderColor;
   }

   public int getOriginalColor() {
       return originalColor;
   }
}

####如何产生气泡呢? 气泡并不是一下子就出来, 而是隔一段时间产生一个, 而这个时间段又不是固定, 需要随机, 可以通过Handler 延迟产生气泡

/**
开始气泡上升动画 
*/
@Override
public void startAnim() {
    if (!mStarting) {
        mStarting = true;
        //此处开始需要通知不断绘制
        invalidate();
        //发送消息生成气泡
        mHandler.sendEmptyMessage(MSG_CREATE_BUBBLE);
    }
}

/**
停止气泡上升动画 
*/
@Override
public void stopAnim() {
    if (mStarting) {
        mStarting = false;
        mHandler.removeMessages(MSG_CREATE_BUBBLE);
        mBubbles.clear();
        invalidate();
    }
}

@Override
public void invalidate() {
    if (mStarting) {
        super.invalidate();
    }
}

创建气泡, 气泡初始配置可根据需要自行设置

private Handler mHandler = new Handler(secondLooper) {
  @Override
  public void handleMessage(Message msg) {
    if (msg.what == MSG_CREATE_BUBBLE) {
      if (mStarting) {
        if (mWidth > 0) {
          Bubble bubble = new Bubble();
          //此处随机气泡半径, 因项目控件width较小, 可自行设置气泡半径随机范围
          int radius = mRandom.nextInt((int) (mWidth / 10f)) + (int)(mWidth / 7);
          bubble.setRadius(radius);
          bubble.setX(mWidth / 2);
          bubble.setY(mHeight);
          // - 0.5f 左右随机摆动
          bubble.setSpeedX((mRandom.nextFloat() - 0.5f) * 2.3f);
          bubble.setSpeedY(mRandom.nextFloat() * 10 + 1);
          //此属性表示不一定所有气泡必须浮出水面才消失(可不要)
          bubble.setRemoveY(mRandom.nextInt(radius));

          //设置气泡颜色, 实际会渐变模糊     

    bubble.setShaderColor(ColorUtil.parseColor(mBubbleColors[mRandom.nextInt(mBubbleColors.length)], "#00"));
          //将气泡添加到集合
          mBubbles.add(bubble);
        }
        //下次产生气泡的时间
        mHandler.sendEmptyMessageDelayed(MSG_CREATE_BUBBLE, (long) ((mRandom.nextInt(2) + 1) * 300));
      }
    }
  }
};

前方高能: 要绘制气泡了! drawBubble(Canvas canvas)

List list = mBubbles;
for (Bubble bubble : list) {
  float y = bubble.getY();
  float x = bubble.getX();
  float speedY = bubble.getSpeedY();
  float speedX = bubble.getSpeedX();
  float radius = bubble.getRadius();

  if (y - bubble.getRemoveY() - speedY <= mTop) {
    //消失后就移除
    mBubbles.remove(bubble);
  } else {
    if ((x + speedX <= radius) || (x + speedX >= mWidth - radius)) {
        //边缘处理 减缓左右摆动幅度
        speedX = -speedX * 0.8f;
        bubble.setSpeedX(speedX);
    }

    //变换x y坐标
    x += speedX;
    y -= speedY;

    //减速 (加速减速可自行设置)
    speedY *= 0.99f;

    if (speedY < 1) {
        //最小上升速度
        speedY = 1;
    }

    bubble.setX(x);
    bubble.setY(y);
    bubble.setSpeedY(speedY);
    //设置气泡渐变
    RadialGradient shader = new RadialGradient(x, y, radius, bubble.getShaderColor(), null, Shader.TileMode.REPEAT);
    mBubblePaint.setShader(shader);
    canvas.drawCircle(x, y, radius, mBubblePaint);

    //气泡碰撞计算
    collision(bubble, x, y, radius);
  }
}

气泡碰撞

private void collision(final Bubble bubble, final float x, final float y, final float radius) {
//开启线程避免界面卡顿
 new Thread(new Runnable() {
  @Override
  public void run() {
    for (Bubble tempBubble : mBubbles) {
      if (tempBubble != bubble) {
        float tempX = tempBubble.getX();
        float tempY = tempBubble.getY();
        float tempRadius = tempBubble.getRadius();
        if (isCollisionWithCircle(x, y, tempX, tempY, radius, tempRadius)) {
          //气泡的半径扩大
          float r = Math.max(radius, tempRadius) * 1.015f;
          float maxRadius = mWidth / 3f;
          if (r > maxRadius){
              r = maxRadius;
          }

          Bubble removeBubble, leaveBubble;
          //移除下面气泡, 保留上面气泡
          if (y < tempY) {
              removeBubble = tempBubble;
              leaveBubble = bubble;
          } else {
              removeBubble = bubble;
              leaveBubble = tempBubble;
          }
          mBubbles.remove(removeBubble);

          //模拟碰撞 方向改变
          float tempLeaveSpeedX = leaveBubble.getSpeedX();
          float leaveSpeedX = Math.abs(tempLeaveSpeedX);
          if (leaveBubble.getX() < removeBubble.getX()) {
              leaveSpeedX = -Math.abs(tempLeaveSpeedX);
          }

          //保留气泡属性设置
          leaveBubble.setRadius(r);
          leaveBubble.setSpeedX(leaveSpeedX * 0.9f);
          //碰撞速度加快
          leaveBubble.setSpeedY((removeBubble.getSpeedY() + leaveBubble.getSpeedY()) / 2f);
          //leaveBubble.setShaderColor(blendColor(leaveBubble.getOriginalColor(), removeBubble.getOriginalColor()));
        }
      }
    }
  }
 }).start();
}

/**
    是否碰撞
*/
private boolean isCollisionWithCircle(float x1, float y1, float x2, float y2, float r1, float r2) {
  //直角坐标系,依点1和点2做平行线,|x1-x2|为横向直角边,|y1-y2|为纵向直角边 依勾股定理 c^2=a^2+b^2
   if (Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) <= (r1 + r2) * 0.85f) {
       // 如果两圆的圆心距小于或等于两圆半径和则认为发生碰撞
       return true;
   }
   return false;
}

最后颜色工具类

/**
 * Created by zengyan on 2017/3/10.
 */
public class ColorUtil {
 /**
   * 颜色设置alpha
   * @param colorString 颜色值 如: "#ffffff"
   * @param alphaString 透明度值 如: "#aa"
   * @return
   */
  public static int parseColor(String colorString, String alphaString) {
    if (colorString.charAt(0) == '#') {
        // Use a long to avoid rollovers on #ffXXXXXX
        long color = Long.parseLong(colorString.substring(1), 16);
        if (colorString.length() == 7) {
            // Set the alpha value
//                alpha |= 0x0000000000000000;
            color = parseColor(color, alphaString);
        } else if (colorString.length() != 9) {
            throw new IllegalArgumentException("Unknown color");
        }
        return (int)color;
    }
    throw new IllegalArgumentException("Unknown color");
  }

  /**
   * 颜色设置alpha
   * @param color 颜色值
   * @param alphaString 透明度值 如: "#aa"
   * @return
   */
  public static int parseColor(long color, String alphaString) {
    if (alphaString.charAt(0) == '#' && alphaString.length() == 3) {
        int alpha = Integer.parseInt(alphaString.substring(1), 16);
        alpha = alpha << 24;
        alpha &= 0x00000000ff000000;
        color |= alpha;
    } else {
        throw new IllegalArgumentException("Unknown alpha");
    }
    return (int)color;
  }
}

代码基本上就是这么多了, 感谢原代码提供者, 希望对有需要的人有帮助, 后面有时间会研究气泡融合的效果, 目前思路为: 气泡碰撞时, 将要被融合的气泡放进 融合气泡内部list, 待canvas画融合气泡连同内部被融合的list一块画使用贝塞尔曲线达到融合效果.

你可能感兴趣的:(android)