气泡随机分布界面的实现
来源:程序员人生 发布时间:2015-05-06 09:19:35 阅读次数:3587次
空话不多说,最近需求要实现1个这样的界面,以下图:
整体界面要求为气泡大致位置在屏幕某个区域,总数固定为8个,但圆心本身位置在该区域内随机,气泡半径,背风景也随机。然后界面展现时有1个漂浮出来的动画效果,气泡之间可以有遮挡但不可以挡住字,点击换1批的时候,重复以上所述动画效果,所有随机值刷新。
先做1个圆形的带阴影的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主要做3件事 画气泡,画阴影,画字,画好以后还对外提供1个漂浮的动画的函数,本来这个函数我写在Activity里让Activity自己控制的,后来想一想根据
单1职责,气泡自己的浮动效果是它自己逻辑,所以应当是它自己对外暴露方法,否则其他Activity需要这个动画效果的时候,就会产生重复代码了。
下面设置1个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个1⑻不重复的随机数组,然后再从色彩数组当选择出不同的色彩。监听器的目的是告知监听者哪一个气泡被点击了~ 终究代码实现效果以下:
还真是没在网上找到类似的demo或例子,算是1次纯原创的尝试吧,谢谢各位观赏~
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠