气泡随机分布界面的实现

        废话不多说,最近需求要实现一个这样的界面,如下图:


        整体界面要求为气泡大致位置在屏幕某个区域,总数固定为8个,但圆心本身位置在该区域内随机,气泡半径,背景色也随机。然后界面展示时有一个漂浮出来的动画效果,气泡之间可以有遮挡但不可以挡住字,点击换一批的时候,重复以上所述动画效果,所有随机值刷新。

        先做一个圆形的带阴影的ImageView:代码如下

package com.amuro.ballonlayout;

import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.Animator.AnimatorListener;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.RelativeLayout.LayoutParams;

public class BallonImageView extends ImageView
{
	private static final int SHADOW_RATIO = 14;
	
	private Paint paintBkg;
	private Paint paintText;
	
	public BallonImageView(Context context)
	{
		this(context, null);
	}
	
	public BallonImageView(Context context, AttributeSet attrs)
	{
		this(context, attrs, 0);
	}

	public BallonImageView(Context context, AttributeSet attrs, int defStyleAttr)
	{
		super(context, attrs, defStyleAttr);
		init();
	}
	
	private void init()
	{
		paintBkg = new Paint();
		paintText = new Paint();
		
		
        paintBkg.setStyle(Paint.Style.FILL);  
        paintBkg.setAntiAlias(true);
        
        paintText.setColor(Color.WHITE);
        paintText.setStyle(Paint.Style.STROKE);
        paintText.setAntiAlias(true);
	}

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);
		
		drawShadow(canvas);
		drawCircleBkg(canvas);
		drawText(canvas);
	}
	
	private void drawShadow(Canvas canvas)
	{
		int viewWidth = getWidth();
		int offsetX = viewWidth / SHADOW_RATIO;
		
		int cx = (viewWidth + offsetX) / 2;
		int cy = cx;
		int radius = (viewWidth - offsetX) / 2;
		
		paintBkg.setColor(Color.parseColor("#E1E1E1"));
		
        canvas.drawCircle(cx, cy, radius, paintBkg);
	}
	
	private void drawCircleBkg(Canvas canvas)
	{
		int viewWidth = getWidth();
		int offsetX = viewWidth / SHADOW_RATIO;

		
		int cx = (viewWidth - offsetX) / 2;
		int cy = cx;
		int radius = cx;
		
		paintBkg.setColor(bkgColor);
        canvas.drawCircle(cx, cy, radius, paintBkg);
		
	}

	private void drawText(Canvas canvas)
	{
		int viewWidth = getWidth();
		int offsetX = viewWidth / SHADOW_RATIO;

		int cx = (viewWidth - offsetX) / 2;
		int cy = cx;
	
		int textSize = getWidth() / 5;
		
		paintText.setTextSize(textSize);
		float textWidth = paintText.measureText(text);
		
		FontMetrics fm = paintText.getFontMetrics();
		int textHeight = (int) Math.ceil(fm.descent - fm.top) + 2;
		
		canvas.drawText(text, cx - (textWidth / 2), cy + (textHeight / 4), paintText);
		
	}

	private String text = "测试测试";
	private int bkgColor = Color.BLACK;
	
	public void setBkgColorAndText(int bkgColor, String text)
	{
		this.bkgColor = bkgColor;
		this.text = text;
		invalidate();
	}
	
	public void setRadius(int radius)
	{
		int diameter = radius * 2;
		LayoutParams params = new LayoutParams(diameter, diameter);
		setLayoutParams(params);
	}
	
	public void floatToPosition(int startX, int startY, int toX, int toY, int duration)
	{
	    AnimatorSet set = new AnimatorSet();
	    set.playTogether(
	    		ObjectAnimator.ofFloat(this, "translationX", startX, toX).setDuration(duration),
	    		ObjectAnimator.ofFloat(this, "translationY", startY, toY).setDuration(duration),
	    		ObjectAnimator.ofFloat(this, "scaleX", 0f, 1f),
	    		ObjectAnimator.ofFloat(this, "scaleY", 0f, 1f));
	    set.setDuration(duration).start();
	    set.addListener(new AnimatorListener()
		{
			
			@Override
			public void onAnimationStart(Animator arg0)
			{
				setEnabled(false);
			}
			
			@Override
			public void onAnimationRepeat(Animator arg0)
			{
				
			}
			
			@Override
			public void onAnimationEnd(Animator arg0)
			{
				setEnabled(true);
			}
			
			@Override
			public void onAnimationCancel(Animator arg0)
			{
				
			}
		});
	}

}


         BallonImageView主要做三件事 画气泡,画阴影,画字,画好之后还对外提供一个漂浮的动画的函数,本来这个函数我写在Activity里让Activity自己控制的,后来想想根据

单一职责,气泡自己的浮动效果是它自己逻辑,所以应该是它自己对外暴露方法,否则其他Activity需要这个动画效果的时候,就会产生重复代码了。

        下面设置一个BallonLayout来放置这些气泡,逻辑比较复杂,大家自己看代码吧,写得好蛋疼……

package com.amuro.ballonlayout;

import java.util.Random;

import com.amuro.balloonlayout.R;
import com.amuro.utils.DisplayUtils;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;

public class BallonLayout extends FrameLayout
{
	private static final int BALLON_COUNT = 8;
	private static final String[] ballonColors = {
		"#f29b76", "#84ccc9", "#f8b551", "#88abda", "#89c997", "#eb6877", "#f39800", "#aa89bd"
	};
	
	/***********监听器相关***************/
	private OnBallonClickListener onBallonClickListener;
	
	public interface OnBallonClickListener
	{
		public void onBallonClick(int id);
	}
	
	public void setOnBallonClickListener(OnBallonClickListener onBallonClickListener)
	{
		this.onBallonClickListener = onBallonClickListener;
	}
	
	private void notifyListener(int id)
	{
		if(this.onBallonClickListener != null)
		{
			this.onBallonClickListener.onBallonClick(id);
		}
	}
	
	/***********界面相关******************/
	private Random random;
	
	//全局值
	private int displayWidth;
	private int ballonFloatStartX;
	private int ballonFloatStartY;
	
	//过渡值
	private int imageView1NowYUsed;
	private int imageView3ToX;
	private int imageView3Radius;
	
	private BallonImageView imageView1;
	private BallonImageView imageView2;
	private BallonImageView imageView3;
	private BallonImageView imageView4;
	private BallonImageView imageView5;
	private BallonImageView imageView6;
	private BallonImageView imageView7;
	private BallonImageView imageView8;
	
	public BallonLayout(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		LayoutInflater.from(context).inflate(R.layout.ballon_layout, this);
		initPoints();
		initView();
	}

	private void initPoints()
	{
		random = new Random();
		
		displayWidth = DisplayUtils.getDisplayWidth(getContext());
		ballonFloatStartX = DisplayUtils.getDisplayWidth(getContext()) / 2;
		ballonFloatStartY = DisplayUtils.getDisplayHeight(getContext());
	}

	private void initView()
	{
		imageView1 = (BallonImageView)findViewById(R.id.iv1);
		imageView2 = (BallonImageView)findViewById(R.id.iv2);
		imageView3 = (BallonImageView)findViewById(R.id.iv3);
		imageView4 = (BallonImageView)findViewById(R.id.iv4);
		imageView5 = (BallonImageView)findViewById(R.id.iv5);
		imageView6 = (BallonImageView)findViewById(R.id.iv6);
		imageView7 = (BallonImageView)findViewById(R.id.iv7);
		imageView8 = (BallonImageView)findViewById(R.id.iv8);
		
		imageView1.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				notifyListener(0);
				
			}
		});
		
		imageView2.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				notifyListener(1);
				
			}
		});
	
		imageView3.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				notifyListener(2);
			}
		});
	
		imageView4.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				notifyListener(3);
			}
		});
		
		imageView5.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				notifyListener(4);
			}
		});
		
		imageView6.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				notifyListener(5);
			}
		});
	
		imageView7.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				notifyListener(6);
			}
		});
		
		imageView8.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				notifyListener(7);
			}
		});
	}
	
	public void setView(String[] ballonNames)
	{
		getNonredundantArray();
		
		setImageView1(ballonNames[0]);
		setImageView2(ballonNames[1]);
		setImageView3(ballonNames[2]);
		setImageView4(ballonNames[3]);
		setImageView5(ballonNames[4]);
		setImageView6(ballonNames[5]);
		setImageView7(ballonNames[6]);
		setImageView8(ballonNames[7]);
	}
	
	private void setImageView1(String ballonName)
	{
		int toX = getBallonToX(displayWidth / 16, displayWidth / 8);
		int radius = getBallon1Radius(toX);
		imageView1.setRadius(radius);
		imageView1.setBkgColorAndText(getRandomColor(0), ballonName);
		imageView1.floatToPosition(ballonFloatStartX, ballonFloatStartY, toX, 100, getRandomDuration());
		
		imageView1NowYUsed = radius * 2 + 100;
	}
	
	private int getBallon1Radius(int toX)
	{	
		int maxRadius = (displayWidth / 2 - toX) / 2;
		int radius = (int) (maxRadius * getRadiusRange(0.8f, 0.85f));
		return radius;
	}
	
	private void setImageView2(String ballonName)
	{
		int toX = getBallonToX((displayWidth / 8) * 5, (displayWidth / 4) * 3);
		int radius = getBallon2Radius(toX);
		imageView2.setRadius(radius);
		imageView2.setBkgColorAndText(getRandomColor(1), ballonName);
		imageView2.floatToPosition(ballonFloatStartX, ballonFloatStartY, toX, 40, getRandomDuration());
		
	}
	
	private int getBallon2Radius(int toX)
	{
		int maxRadius = (displayWidth - toX) / 2;
		int radius = (int) (maxRadius * getRadiusRange(0.7f, 0.8f));
		return radius;
	}
	
	private void setImageView3(String ballonName)
	{
		imageView3ToX = getBallonToX((displayWidth / 16) * 5, (displayWidth / 16) * 7);
		imageView3Radius = getBallon3Radius();
		int toY = imageView1NowYUsed + 20;
		imageView3.setRadius(imageView3Radius);
		imageView3.setBkgColorAndText(getRandomColor(2), ballonName);
		imageView3.floatToPosition(ballonFloatStartX, ballonFloatStartY, imageView3ToX, toY, getRandomDuration());
		
		imageView1NowYUsed = toY + imageView3Radius * 2;
	}
	
	private int getBallon3Radius()
	{
		int maxRadius = displayWidth / 8;
		int radius = (int) (maxRadius * getRadiusRange(0.85f, 0.95f));
		return radius;
	}
	
	private void setImageView4(String ballonName)
	{
		int toX = getBallonToX(imageView3ToX + imageView3Radius * 2 + 30, (displayWidth / 4) * 3);
		int radius = getBallon4Radius();
		int toY = imageView1NowYUsed - imageView3Radius * 2;
		imageView4.setRadius(radius);
		imageView4.setBkgColorAndText(getRandomColor(3), ballonName);
		imageView4.floatToPosition(ballonFloatStartX, ballonFloatStartY, toX, toY, getRandomDuration());
	}
	
    private int getBallon4Radius()
	{
    	int maxRadius = (displayWidth / 16) * 3;
		int radius = (int) (maxRadius * getRadiusRange(0.8f, 0.95f));
		return radius;
	}

	private void setImageView5(String ballonName)
	{
		int toX = getBallonToX(displayWidth / 16, displayWidth / 8);
		int radius = getBallon3Radius();
		int toY = imageView1NowYUsed + 10;
		
		imageView5.setRadius(radius);
		imageView5.setBkgColorAndText(getRandomColor(4), ballonName);
		imageView5.floatToPosition(ballonFloatStartX, ballonFloatStartY, toX, toY, getRandomDuration());
		
		imageView1NowYUsed = imageView1NowYUsed + radius * 2;
	}

	private void setImageView6(String ballonName)
	{
		int toX = getBallonToX((displayWidth / 16) * 6, (displayWidth / 16) * 8);
		int radius = getBallon3Radius();
		int toY = imageView1NowYUsed - radius;
		
		imageView6.setRadius(radius);
		imageView6.setBkgColorAndText(getRandomColor(5), ballonName);
		imageView6.floatToPosition(ballonFloatStartX, ballonFloatStartY, toX, toY, getRandomDuration());
		
		imageView1NowYUsed = toY + radius * 2;
	}
	
	private void setImageView7(String ballonName)
	{
		int toX = getBallonToX((displayWidth / 16) * 11, (displayWidth / 16) * 12);
		int radius = getBallon3Radius();
		int toY = imageView1NowYUsed - radius;
		
		imageView7.setRadius(radius);
		imageView7.setBkgColorAndText(getRandomColor(6), ballonName);
		imageView7.floatToPosition(ballonFloatStartX, ballonFloatStartY, toX, toY, getRandomDuration());
		
		imageView1NowYUsed = toY + radius * 2;
	}
	
	private void setImageView8(String ballonName)
	{
		int toX = getBallonToX(displayWidth / 8, (displayWidth / 16) * 4);
		int radius = getBallon8Radius();
		int toY = imageView1NowYUsed - radius;
		
		imageView8.setRadius(radius);
		imageView8.setBkgColorAndText(getRandomColor(7), ballonName);
		imageView8.floatToPosition(ballonFloatStartX, ballonFloatStartY, toX, toY, getRandomDuration());
	}
	
	private int getBallon8Radius()
	{
		int maxRadius = (displayWidth / 24) * 4;
		int radius = (int) (maxRadius * getRadiusRange(0.8f, 0.95f));
		return radius;
	}
	
	private int getBallonToX(int start, int end)
	{
		int toX = random.nextInt(end);
		
		if(toX < start)
		{
			toX = start;
		}
		
		return toX;
	}
	
	private float getRadiusRange(float minRange, float maxRange)
	{
		float range = random.nextFloat();
		
		if(range <= minRange)
		{
			range = minRange;
		}
		
		if(range >= maxRange)
		{
			range = maxRange;
		}
		
		return range;
	}

	private int[] colorIndex = new int[BALLON_COUNT];
	private int getRandomColor(int index)
	{
		return Color.parseColor(ballonColors[colorIndex[index]]);
	}
	
	private int getRandomDuration()
	{
		float seconds = (random.nextInt(50) + 51) / 100f;
		return (int) (seconds * 2000);
	}
	
	private void getNonredundantArray()
	{

		// 初始化备选数组
		int[] defaultNums = new int[BALLON_COUNT];

		for (int i = 0; i < defaultNums.length; i++)
		{
			defaultNums[i] = i;
		}

		// 默认数组中可以选择的部分长度
		int canBeUsed = BALLON_COUNT;
		// 填充目标数组
		for (int i = 0; i < colorIndex.length; i++)
		{
			// 将随机选取的数字存入目标数组
			int index = random.nextInt(canBeUsed);
			colorIndex[i] = defaultNums[index];
			// 将已用过的数字扔到备选数组最后,并减小可选区域
			swap(index, canBeUsed - 1, defaultNums);
			canBeUsed--;
		}

	}
	
	private void swap(int i, int j, int[] nums)
	{
		int temp = nums[i];
		nums[i] = nums[j];
		nums[j] = temp;
	}

}


        其中的getNonredundantArray()通过洗牌算法,保证每次生成一个1-8不重复的随机数组,然后再从颜色数组中选择出不同的颜色。监听器的目的是告诉监听者哪个气泡被点击了~ 最终代码实现效果如下:

         还真是没在网上找到类似的demo或者例子,算是一次纯原创的尝试吧,谢谢各位观赏~

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值