因为项目需要,去学习了自定义ViewGroup,第一次写自定义控件,算是给自己记个笔记,也希望可以帮到有需要的朋友,如果有写的不好的地方,还请大神指导。有问题可以加群:421448830 交流。
1、先看看效果图
2、自定义ViewGroup的代码:因为我们项目需求最多是四个,所以我只写了四个的情况,如果有其他的需求,可在此基础上修改,不过这个不建议过多数量的子布局,少量可以使用。且计算时要注意当出现最多子view的数量的时候,父布局的宽高要能放的下所有的子view。
public class MyViewGroup extends ViewGroup {
private static final String TAG = "My_ViewGroup";
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**
* 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
// 计算出所有的childView的宽和高
measureChildren(widthMeasureSpec, heightMeasureSpec);
/**
* 记录如果是wrap_content是设置的宽和高
*/
int width = 0;
int height = 0;
int cCount = getChildCount();
int cWidth = 0;
int cHeight = 0;
MarginLayoutParams cParams = null;
// 用于计算左边两个childView的高度
int lHeight = 0;
// 用于计算右边两个childView的高度,最终高度取二者之间大值
int rHeight = 0;
// 用于计算上边两个childView的宽度
int tWidth = 0;
// 用于计算下面两个childiew的宽度,最终宽度取二者之间大值
int bWidth = 0;
/**
* 根据childView计算的出的宽和高,以及设置的margin计算容器的宽和高,主要用于容器是warp_content时
*/
for (int i = 0; i < cCount; i++) {
View childView = getChildAt(i);
cWidth = childView.getMeasuredWidth();
cHeight = childView.getMeasuredHeight();
cParams = (MarginLayoutParams) childView.getLayoutParams();
// 上面两个childView
if (i == 0 || i == 1) {
tWidth += cWidth + cParams.leftMargin + cParams.rightMargin;
}
if (i == 2 || i == 3) {
bWidth += cWidth + cParams.leftMargin + cParams.rightMargin;
}
if (i == 0 || i == 2) {
lHeight += cHeight + cParams.topMargin + cParams.bottomMargin;
}
if (i == 1 || i == 3) {
rHeight += cHeight + cParams.topMargin + cParams.bottomMargin;
}
}
width = Math.max(tWidth, bWidth);
height = Math.max(lHeight, rHeight);
/**
* 如果是wrap_content设置为我们计算的值
* 否则:直接设置为父容器计算的值
*/
setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
: width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
: height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int marginTop = 0; //最大的marginTop值
int cCount = getChildCount();
int cWidth = 0;
int cHeight = 0;
MarginLayoutParams cParams = null;
int top_one = 0;
int top_two = 0;
int top_three = 0;
/**
* 遍历所有childView根据其宽和高,以及margin进行布局
*/
for (int i = 0; i < cCount; i++) {
View childView = getChildAt(i);
cWidth = childView.getMeasuredWidth();
cHeight = childView.getMeasuredHeight();
cParams = (MarginLayoutParams) childView.getLayoutParams();
int cl = 0, ct = 0, cr = 0, cb = 0;
switch (cCount) {
case 1:
marginTop = getHeight() - cHeight - cParams.bottomMargin;
break;
case 2:
marginTop = (getHeight() / 2) - cHeight - cParams.bottomMargin;
break;
case 3:
marginTop = (getHeight() / 3) - cHeight - cParams.bottomMargin;
break;
case 4:
marginTop = (getHeight() / 4) - cHeight - cParams.bottomMargin;
break;
}
switch (i) {
case 0:
top_one = (int) (Math.random() * marginTop);
cl = (int) (Math.random() * (getWidth() - cWidth - cParams.rightMargin));
ct = top_one;
break;
case 1:
top_two = top_one + cHeight + (int) (Math.random() * marginTop);
cl = (int) (Math.random() * (getWidth() - cWidth - cParams.rightMargin));
ct = top_two;
break;
case 2:
top_three = top_two + cHeight + (int) (Math.random() * marginTop);
cl = (int) (Math.random() * (getWidth() - cWidth - cParams.rightMargin));
ct = top_three;
break;
case 3:
cl = (int) (Math.random() * (getWidth() - cWidth - cParams.rightMargin));
ct = top_three + cHeight + (int) (Math.random() * marginTop);
break;
}
cr = cl + cWidth;
cb = cHeight + ct;
childView.layout(cl, ct, cr, cb);
}
}
}
3、activity_main.xml布局:使用这个自定义控件,其宽高最好给固定值或者match_parent,因为每一次出现的子布局数量不定,需要根据父布局的宽高决定其位置的随机数。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:orientation="vertical">
<com.xiaoxiao.myviewgroup.MyViewGroup
android:id="@+id/my_viewgroup"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/btn_start"/>
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:text="开始" />
</RelativeLayout>
4、每一个随机冒出的泡泡的布局,item_viewgroup_paopao.xml布局,这个可以根据自己的需要去写。其中CircleImageView是圆形头像的自定义控件。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="-20dp"
android:layout_toRightOf="@+id/img_head"
android:background="@drawable/shape_paopao_background"
android:orientation="vertical">
<TextView
android:id="@+id/tv_get_task_nick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp"
android:text="ID:2***3"
android:textColor="#ef4359"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_get_task_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp"
android:text="冒出一个快乐泡泡"
android:textColor="#4A4A4B"
android:textSize="12sp" />
</LinearLayout>
<com.xiaoxiao.myviewgroup.CircleImageView
android:id="@+id/img_head"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
android:src="@drawable/default_head" />
</RelativeLayout>
5、MainActivity的代码,PaopaoBean是一个实体类,用来存放数据,可以根据具体情况去写。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private final static String TAG = "ViewGroupActivity";
List<PaopaoBean> mList = new ArrayList<>();
private RelativeLayout relativeLayout;
private boolean isStart = true;
private AlphaAnimation alphaAnimation;
MyViewGroup my_viewgroup;
Button btn_start;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
my_viewgroup = (MyViewGroup) findViewById(R.id.my_viewgroup);
btn_start = (Button) findViewById(R.id.btn_start);
btn_start.setText("开始");
btn_start.setOnClickListener(this);
}
private void showDate() {
mList.clear();
Random random = new Random();
int count = random.nextInt(4) + 1;
Log.e(TAG, "count==" + count);
int id_front;
int id_after;
int task_type;
String task_name = "";
for (int i = 0; i < count; i++) {
task_type = random.nextInt(3);
id_front = random.nextInt(9) + 1;
id_after = random.nextInt(10);
switch (task_type) {
case 0:
task_name = "开心";
break;
case 1:
task_name = "快乐";
break;
case 2:
task_name = "幸福";
break;
}
mList.add(new PaopaoBean("ID:" + id_front + "***" + id_after, "冒出一个" + task_name + "泡泡"));
}
getPaopao(mList, my_viewgroup);
}
private void getPaopao(List<PaopaoBean> paopao, MyViewGroup viewgroup) {
viewgroup.removeAllViews();
for (int i = 0; i < paopao.size(); i++) {
relativeLayout = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.item_viewgroup_paopao, my_viewgroup, false);
ImageView img_head = (ImageView) relativeLayout.findViewById(R.id.img_head);
TextView tv_nick = (TextView) relativeLayout.findViewById(R.id.tv_get_task_nick);
TextView tv_type = (TextView) relativeLayout.findViewById(R.id.tv_get_task_type);
viewgroup.addView(relativeLayout);
Glide.with(this).load(R.drawable.default_head).into(img_head);
tv_nick.setText(paopao.get(i).getTv_nick());
tv_type.setText(paopao.get(i).getTv_type());
}
generateAlphaAnimation();
}
//渐变动画
private void generateAlphaAnimation() {
Log.e(TAG, "222");
alphaAnimation = new AlphaAnimation(1f, 0); //渐变动画
alphaAnimation.setDuration(1500);
alphaAnimation.setStartOffset(1500);
alphaAnimation.start();
my_viewgroup.startAnimation(alphaAnimation);
alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (!isStart) {
alphaAnimation.cancel();
my_viewgroup.clearAnimation();
mList.clear();
my_viewgroup.removeAllViews();
relativeLayout.removeAllViews();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (!isStart) {
showDate();
}
}
}, 2000);
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
@Override
public void onClick(View v) {
if (isStart) {
btn_start.setText("停止");
isStart = false;
showDate();
} else {
alphaAnimation.cancel();
my_viewgroup.clearAnimation();
mList.clear();
my_viewgroup.removeAllViews();
btn_start.setText("开始");
relativeLayout.removeAllViews();
isStart = true;
}
}
}
6、PaopaoBean 类:
public class PaopaoBean {
private String tv_nick;
private String tv_type;
public PaopaoBean( String tv_nick, String tv_type) {
this.tv_nick = tv_nick;
this.tv_type = tv_type;
}
public String getTv_nick() {
return tv_nick;
}
public void setTv_nick(String tv_nick) {
this.tv_nick = tv_nick;
}
public String getTv_type() {
return tv_type;
}
public void setTv_type(String tv_task) {
this.tv_type = tv_task;
}
}
源码地址:http://blog.csdn.net/miaoxiaomo/article/details/52936832