本篇文章讲的是Android 自定义ViewGroup之实现标签流式布局-FlowLayout,开发中我们会经常需要实现类似于热门标签等自动换行的流式布局的功能,网上也有很多这样的FlowLayout,但不影响我对其的学习。和往常一样,主要还是想总结一下自定义ViewGroup的开发过程以及一些需要注意的地方。

按照惯例,我们先来看看效果图

一、写代码之前,有几个是问题是我们先要弄清楚的:
1、什么是ViewGroup:从名字上来看,它可以被翻译为控件组,言外之意是ViewGroup内部包含了许多个控件,是一组View。在Android的设计中,ViewGroup也继承了View,这就意味着View本身就可以是单个控件也可以是由多个控件组成的一组控件;

2、ViewGroup的种类:常见的有LinearLayout、RelativeLayout、FrameLayout、AbsoluteLayout、GirdLayout、TableLayout。其中LinearLayout和RelativeLayout使用的最多的两种;

3、ViewGroup的职责:给childView计算出建议的宽和高和测量模式 ,然后决定childView的位置;

4、话说何为流式布局(FlowLayout):就是控件根据ViewGroup的宽,自动的从左往右添加。如果当前行还能放得这个子View,就放到当前行,如果当前行剩余的空间不足于容纳这个子View,则自动添加到下一行的最左边;

二、先总结下自定义ViewGroup的步骤:
1、自定义ViewGroup的属性
2、在ViewGroup的构造方法中获得我们自定义的属性
3、重写onMesure
4、重写onLayout

三、ViewGroup的几个构造函数:
1、public FlowLayout(Context context)
—>Java代码直接new一个FlowLayout实例的时候,会调用这个只有一个参数的构造函数;
2、public FlowLayout(Context context, AttributeSet attrs)
—>在默认的XML布局文件中创建的时候调用这个有两个参数的构造函数。AttributeSet类型的参数负责把XML布局文件中所自定义的属性通过AttributeSet带入到View内;
3、public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr)
—>构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或者Activity所用的Theme中的默认Style,且只有在明确调用的时候才会调用
4、public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
—>该构造函数是在API21的时候才添加上的

四、下面我们就开始来看看自定义ViewGroup的主要代码啦
1、自定义ViewGroup的属性,首先在res/values/ 下建立一个attr.xml , 在里面定义我们的需要用到的属性以及声明相对应属性的取值类型

<?xml version="1.0" encoding="utf-8"?>
<resources><!--每个item纵向间距--><attr name="verticalSpacing" format="dimension" /><!-- 每个item横向间距--><attr name="horizontalSpacing" format="dimension" /><declare-styleable name="FlowLayout"><attr name="verticalSpacing" /><attr name="horizontalSpacing" /></declare-styleable></resources>

我们定义了verticalSpacing以及horizontalSpacing2个属性,分别表示每个标签之间纵向间距和横向间距,其中format是值该属性的取值类型,format取值类型总共有10种,包括:string,color,demension,integer,enum,reference,float,boolean,fraction和flag。

2、然后在XML布局中声明我们的自定义View

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:custom="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextView
        android:layout_width="match_parent"android:layout_height="48dp"android:background="#38353D"android:gravity="center"android:text="标签"android:textColor="@android:color/white"android:textSize="16dp" /><ScrollView
        android:layout_width="match_parent"android:layout_height="wrap_content"><LinearLayout
            android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><TextView
                android:id="@+id/tv_remind"android:layout_width="match_parent"android:layout_height="46dp"android:background="@android:color/white"android:gravity="center_vertical"android:paddingLeft="15dp"android:text="我的标签(最多5个) "android:textSize="16dp" /><com.per.flowlayoutdome.FlowLayout
                android:id="@+id/tcy_my_label"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/white"android:padding="5dp"android:visibility="gone"custom:horizontalSpacing="6dp"custom:verticalSpacing="12dp" /><View
                android:layout_width="match_parent"android:layout_height="10dp"android:background="#f6f6f6" /><RelativeLayout
                android:layout_width="match_parent"android:layout_height="46dp"android:background="@android:color/white"><TextView
                    android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerVertical="true"android:paddingLeft="15dp"android:text="推荐标签 "android:textSize="16dp" /></RelativeLayout><View
                android:layout_width="match_parent"android:layout_height="1dp"android:background="#f6f6f6" /><com.per.flowlayoutdome.FlowLayout
                android:id="@+id/tcy_hot_label"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/white"android:padding="5dp"custom:horizontalSpacing="6dp"custom:verticalSpacing="12dp" /></LinearLayout></ScrollView>
</LinearLayout>

一定要引入xmlns:custom=”http://schemas.android.com/apk/res-auto”,Android Studio中我们可以使用res-atuo命名空间,就不用在添加自定义View全类名。

3、在View的构造方法中,获得我们的自定义的样式

/*** 每个item纵向间距*/private int mVerticalSpacing;/*** 每个item横向间距*/private int mHorizontalSpacing;public FlowLayout(Context context) {this(context, null);}public FlowLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public FlowLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);/*** 获得我们所定义的自定义样式属性*/TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlowLayout, defStyle, 0);for (int i = 0; i < a.getIndexCount(); i++) {int attr = a.getIndex(i);switch (attr) {case R.styleable.FlowLayout_verticalSpacing:mVerticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_verticalSpacing, 5);break;case R.styleable.FlowLayout_horizontalSpacing:mHorizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_horizontalSpacing, 10);break;}}a.recycle();}

我们重写了3个构造方法,在上面的构造方法中说过默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造方法调用三个参数的构造方法,然后在三个参数的构造方法中获得自定义属性。

一开始一个参数的构造方法和两个参数的构造方法是这样的:

 public FlowLayout(Context context) {super(context);}public FlowLayout(Context context, AttributeSet attrs) {super(context, attrs);}

有一点要注意的是super应该改成this,然后让一个参数的构造方法引用两个参数的构造方法,两个参数的构造方法引用三个参数的构造方法,代码如下:

 public FlowLayout(Context context) {this(context, null);}public FlowLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}

4、重写onMesure方法

/*** 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {/*** 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式*/int heighMode = MeasureSpec.getMode(heightMeasureSpec);int heighSize = MeasureSpec.getSize(heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);/*** 高*/int height = 0;/*** 每一行的高度,累加至height*/int lineHeight = 0;/*** 在warp_content情况下,记录当前childView的左边的一个位置*/int childLeft = getPaddingLeft();/*** 在warp_content情况下,记录当前childView的上边的一个位置*/int childTop = getPaddingTop();// getChildCount得到子view的数目,遍历循环出每个子Viewfor (int i = 0; i < getChildCount(); i++) {//拿到index上的子viewView childView = getChildAt(i);// 测量每一个child的宽和高measureChild(childView, widthMeasureSpec, heightMeasureSpec);//当前子空间实际占据的高度int childHeight = childView.getMeasuredHeight();//当前子空间实际占据的宽度int childWidth = childView.getMeasuredWidth();lineHeight = Math.max(childHeight, lineHeight);// 取最大值//如果加入当前childView,超出最大宽度,则将目前最大宽度给width,类加height 然后开启新行if (childWidth + childLeft + getPaddingRight() > widthSize) {childLeft = getPaddingLeft();// 重新开启新行,开始记录childLeftchildTop += mVerticalSpacing + childHeight;// 叠加当前的高度lineHeight = childHeight;// 开启记录下一行的高度}else{//否则累加当前childView的宽度childLeft += childWidth + mHorizontalSpacing;}}height += childTop + lineHeight + getPaddingBottom();setMeasuredDimension(widthSize, heighMode == MeasureSpec.EXACTLY ? heighSize : height);}

首先首先得到其父容器传入的测量模式和宽高的计算值,然后遍历所有的childView,使用measureChild方法对所有的childView进行测量。然后根据所有childView的测量得出的高得到该ViewGroup如果设置为wrap_content时的高。最后根据模式,如果是MeasureSpec.EXACTLY则直接使用父ViewGroup传入的高,否则设置为自己计算的高,细心的朋友会问,那儿宽呢,在这里我们默认宽为MeasureSpec.EXACTLY模式。

5、重写onLayout方法

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int width = r - l;int childLeft = getPaddingLeft();int childTop = getPaddingTop();int lineHeight = 0;//遍历所有childView根据其宽和高,计算子控件应该出现的位置for (int i = 0; i < getChildCount(); i++) {final View childView = getChildAt(i);if (childView.getVisibility() == View.GONE) {continue;}int childWidth = childView.getMeasuredWidth();int childHeight = childView.getMeasuredHeight();lineHeight = Math.max(childHeight, lineHeight);// 如果已经需要换行if (childLeft + childWidth + getPaddingRight() > width) {childLeft = getPaddingLeft();childTop += mVerticalSpacing + lineHeight;lineHeight = childHeight;}childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);childLeft += childWidth + mHorizontalSpacing;}}

onLayout中完成对所有childView的位置以及大小的指定

6、到此,我们对自定义ViewGroup的代码已经写完了,有几点要注意的:
(1)getChildAt(int index):获得index上的子view;
(2)getChildCount():得到所有子view的数目;
(3)measureChild(childView, widthMeasureSpec, heightMeasureSpec):使用子view自身的测量方法,测量每一个child的宽和高;

回归到主题,现在我们把自定义ViewGroup,实现FlowLayout的部分完成了,接下来的就是一些逻辑代码了

五、下面就是一些逻辑代码啦
1、我把FlowLayout里面完整的代码贴出来:

package com.per.flowlayoutdome;import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;/*** @author: xiaolijuan* @description: 流式布局-标签流容器* @projectName: FlowLayoutDome* @date: 2016-06-16* @time: 16:21*/
public class FlowLayout extends ViewGroup{/*** 每个item纵向间距*/private int mVerticalSpacing;/*** 每个item横向间距*/private int mHorizontalSpacing;private BaseAdapter mAdapter;private TagItemClickListener mListener;private DataChangeObserver mObserver;public FlowLayout(Context context) {this(context, null);}public FlowLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public FlowLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);/*** 获得我们所定义的自定义样式属性*/TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlowLayout, defStyle, 0);for (int i = 0; i < a.getIndexCount(); i++) {int attr = a.getIndex(i);switch (attr) {case R.styleable.FlowLayout_verticalSpacing:mVerticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_verticalSpacing, 5);break;case R.styleable.FlowLayout_horizontalSpacing:mHorizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_horizontalSpacing, 10);break;}}a.recycle();}/*** 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {/*** 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式*/int heighMode = MeasureSpec.getMode(heightMeasureSpec);int heighSize = MeasureSpec.getSize(heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);/*** 高*/int height = 0;/*** 每一行的高度,累加至height*/int lineHeight = 0;/*** 在warp_content情况下,记录当前childView的左边的一个位置*/int childLeft = getPaddingLeft();/*** 在warp_content情况下,记录当前childView的上边的一个位置*/int childTop = getPaddingTop();// getChildCount得到子view的数目,遍历循环出每个子Viewfor (int i = 0; i < getChildCount(); i++) {//拿到index上的子viewView childView = getChildAt(i);// 测量每一个child的宽和高measureChild(childView, widthMeasureSpec, heightMeasureSpec);//当前子空间实际占据的高度int childHeight = childView.getMeasuredHeight();//当前子空间实际占据的宽度int childWidth = childView.getMeasuredWidth();lineHeight = Math.max(childHeight, lineHeight);// 取最大值//如果加入当前childView,超出最大宽度,则将目前最大宽度给width,类加height 然后开启新行if (childWidth + childLeft + getPaddingRight() > widthSize) {childLeft = getPaddingLeft();// 重新开启新行,开始记录childLeftchildTop += mVerticalSpacing + childHeight;// 叠加当前的高度lineHeight = childHeight;// 开启记录下一行的高度}else{//否则累加当前childView的宽度childLeft += childWidth + mHorizontalSpacing;}}height += childTop + lineHeight + getPaddingBottom();setMeasuredDimension(widthSize, heighMode == MeasureSpec.EXACTLY ? heighSize : height);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int width = r - l;int childLeft = getPaddingLeft();int childTop = getPaddingTop();int lineHeight = 0;//遍历所有childView根据其宽和高,计算子控件应该出现的位置for (int i = 0; i < getChildCount(); i++) {final View childView = getChildAt(i);if (childView.getVisibility() == View.GONE) {continue;}int childWidth = childView.getMeasuredWidth();int childHeight = childView.getMeasuredHeight();lineHeight = Math.max(childHeight, lineHeight);// 如果已经需要换行if (childLeft + childWidth + getPaddingRight() > width) {childLeft = getPaddingLeft();childTop += mVerticalSpacing + lineHeight;lineHeight = childHeight;}childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);childLeft += childWidth + mHorizontalSpacing;}}private void drawLayout() {if (mAdapter == null || mAdapter.getCount() == 0) {return;}removeAllViews();for (int i = 0; i < mAdapter.getCount(); i++) {View view = mAdapter.getView(i, null, null);final int position = i;view.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (mListener != null) {mListener.itemClick(position);}}});addView(view);}}public void setAdapter(BaseAdapter adapter) {if (mAdapter == null) {mAdapter = adapter;if (mObserver == null) {mObserver = new DataChangeObserver();mAdapter.registerDataSetObserver(mObserver);}drawLayout();}}public void setItemClickListener(TagItemClickListener mListener) {this.mListener = mListener;}public interface TagItemClickListener {void itemClick(int position);}class DataChangeObserver extends DataSetObserver {@Overridepublic void onChanged() {drawLayout();}@Overridepublic void onInvalidated() {super.onInvalidated();}}
}

2、FlowLayoutAdapter

package com.per.flowlayoutdome;import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;import java.util.List;/*** @author: adan* @description: 流式布局适配器* @projectName: FlowLayoutDome* @date: 2016-06-16* @time: 16:22*/
public class FlowLayoutAdapter extends BaseAdapter {private Context mContext;private List<String> mList;public FlowLayoutAdapter(Context context, List<String> list) {mContext = context;mList = list;}@Overridepublic int getCount() {return mList.size();}@Overridepublic String getItem(int position) {return mList.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;if (convertView == null) {convertView = LayoutInflater.from(mContext).inflate(R.layout.item_tag, null);holder = new ViewHolder();holder.mBtnTag = (Button) convertView.findViewById(R.id.btn_tag);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}holder.mBtnTag.setText(getItem(position));return convertView;}static class ViewHolder {Button mBtnTag;}
}

3、MainActivity

package com.per.flowlayoutdome;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;import java.util.ArrayList;
import java.util.List;public class MainActivity extends Activity {private TextView tv_remind;private FlowLayout tcy_my_label, tcy_hot_label;private FlowLayoutAdapter mMyLabelAdapter, mHotLabelAdapter;private List<String> MyLabelLists, HotLabelLists;private static int TAG_REQUESTCODE = 0x101;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();initData();}private void initView() {tv_remind = (TextView) findViewById(R.id.tv_remind);tcy_my_label = (FlowLayout) findViewById(R.id.tcy_my_label);tcy_hot_label = (FlowLayout) findViewById(R.id.tcy_hot_label);}private void initData() {String[] date = getResources().getStringArray(R.array.tags);HotLabelLists = new ArrayList<>();for (int i = 0; i < date.length; i++) {HotLabelLists.add(date[i]);}mHotLabelAdapter = new FlowLayoutAdapter(this, HotLabelLists);tcy_hot_label.setAdapter(mHotLabelAdapter);tcy_hot_label.setItemClickListener(new TagCloudLayoutItemOnClick(1));MyLabelLists = new ArrayList<>();mMyLabelAdapter = new FlowLayoutAdapter(this, MyLabelLists);tcy_my_label.setAdapter(mMyLabelAdapter);tcy_my_label.setItemClickListener(new TagCloudLayoutItemOnClick(0));String labels = String.valueOf(getIntent().getStringExtra("labels"));if (!TextUtils.isEmpty(labels) && labels.length() > 0&& !labels.equals("null")) {String[] temp = labels.split(",");for (int i = 0; i < temp.length; i++) {MyLabelLists.add(temp[i]);}ChangeMyLabels();}}/*** 刷新我的标签数据*/private void ChangeMyLabels() {tv_remind.setVisibility(MyLabelLists.size() > 0 ? View.GONE: View.VISIBLE);tcy_my_label.setVisibility(MyLabelLists.size() > 0 ? View.VISIBLE: View.GONE);mMyLabelAdapter.notifyDataSetChanged();}/*** 标签的点击事件** @author lijuan*/class TagCloudLayoutItemOnClick implements FlowLayout.TagItemClickListener {int index;public TagCloudLayoutItemOnClick(int index) {this.index = index;}@Overridepublic void itemClick(int position) {switch (index) {case 0:MyLabelLists.remove(MyLabelLists.get(position));ChangeMyLabels();break;case 1:if (MyLabelLists.size() < 5) {if (HotLabelLists.get(position).equals("自定义")) {startActivityForResult(new Intent(MainActivity.this,AddTagActivity.class),TAG_REQUESTCODE);} else {Boolean isExits = isExist(MyLabelLists,HotLabelLists.get(position));if (isExits) {Toast.makeText(MainActivity.this, "此标签已经添加啦", Toast.LENGTH_LONG).show();return;}MyLabelLists.add(HotLabelLists.get(position));ChangeMyLabels();}} else {Toast.makeText(MainActivity.this, "最多只能添加5个标签", Toast.LENGTH_LONG).show();}break;default:break;}}}/*** 将数组里面的字符串遍历一遍,看是否存在相同标签** @param str* @param compareStr* @return*/public static Boolean isExist(List<String> str, String compareStr) {Boolean isExist = false;//默认沒有相同标签for (int i = 0; i < str.size(); i++) {if (compareStr.equals(str.get(i))) {isExist = true;}}return isExist;}/*** 回传数据*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if (TAG_REQUESTCODE == requestCode) {if (resultCode == AddTagActivity.TAG_RESULTCODE) {String label = data.getStringExtra("tags");MyLabelLists.add(label);ChangeMyLabels();}}}
}

4、AddTagActivity

package com.per.flowlayoutdome;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;/*** @author: xiaolijuan* @description: 添加自定义标签* @date: 2016-06-10* @time: 14:37*/
public class AddTagActivity extends Activity implements View.OnClickListener{private EditText mEtLabel;private Button mBtnSure;public final static int TAG_RESULTCODE = 0x102;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_add_tag);initView();initData();}private void initData() {// 根据输入框输入值的改变提示最大允许输入的个数mEtLabel.addTextChangedListener(new TextWatcher_Enum());}private void initView() {mEtLabel = (EditText) findViewById(R.id.et_label);mBtnSure = (Button) findViewById(R.id.btn_sure);mBtnSure.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_sure:String label = mEtLabel.getText().toString();if (TextUtils.isEmpty(label)) {Toast.makeText(AddTagActivity.this,"自定义标签不应为空",Toast.LENGTH_LONG).show();return;}Intent intent = getIntent();intent.putExtra("tags", label);setResult(TAG_RESULTCODE, intent);finish();break;}}/*** 根据输入框输入值的长度超过8个字符的时候,弹出输入的标签应控制在8个字** @author lijuan**/class TextWatcher_Enum implements TextWatcher {@Overridepublic void beforeTextChanged(CharSequence s, int start, int count,int after) {}@Overridepublic void onTextChanged(CharSequence s, int start, int before,int count) {int lenght = mEtLabel.getText().toString().trim().length();if (lenght > 8) {Toast.makeText(AddTagActivity.this,"输入的标签应控制在8个字",Toast.LENGTH_LONG).show();}}@Overridepublic void afterTextChanged(Editable s) {}}
}

6、activity_main.xml在上面已经贴出来了,在这里就不重复了,我们创建了arrays.xml,在这里定义了一写热门的标签:

<?xml version="1.0" encoding="UTF-8"?>
<resources><string-array name="tags"><item>美妆</item><item>画板</item><item>漫画</item><item>高科技</item><item>韩国电影</item><item>股票</item><item>美术</item><item>高富帅</item><item>鸿泰安</item><item>运动</item><item>外语</item><item>财经</item><item>大叔</item><item>非主流</item><item>暴走漫画</item><item>心理学</item><item>汉语</item><item>白富美</item><item>自定义</item></string-array></resources>

7、item_tag.xml

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/btn_tag"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/selector_btn_item"android:gravity="center"android:minHeight="30dp"android:minWidth="45dp"android:paddingLeft="16dp"android:paddingRight="16dp"android:textSize="12sp" />

8、activity_add_tag.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="250dp"android:layout_height="wrap_content"android:background="@android:color/white"android:gravity="center_horizontal"android:orientation="vertical"android:padding="5dp" ><LinearLayout
        android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical" ><TextView
            android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:text="请输入想要添加的标签"android:textColor="@android:color/black"android:textSize="16dp" /><EditText
            android:id="@+id/et_label"android:layout_width="match_parent"android:layout_height="80dp"android:layout_margin="5dp"android:background="@drawable/selector_btn_item"android:gravity="center_vertical|start"android:maxLength="8"android:paddingLeft="10dp"android:textColor="@android:color/black"android:textSize="16dp" /></LinearLayout><Button
        android:id="@+id/btn_sure"android:layout_width="50dp"android:layout_height="32dp"android:layout_marginLeft="16dp"android:layout_marginRight="16dp"android:layout_marginTop="5dp"android:background="#38353D"android:gravity="center"android:text="确定"android:textColor="@android:color/white"android:textSize="14dp" /></LinearLayout>

9、selector_btn_item.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_pressed="true"><shape><solid android:color="#ff76787b" /><corners android:bottomLeftRadius="5dp" android:bottomRightRadius="5dp" android:topLeftRadius="5dp" android:topRightRadius="5dp" /><stroke android:width="1px" android:color="#ffd1d1d1" /></shape></item><item><shape><solid android:color="#ffffff" /><corners android:bottomLeftRadius="2.5dp" android:bottomRightRadius="2.5dp" android:topLeftRadius="2.5dp" android:topRightRadius="2.5dp" /><stroke android:width="0.5px" android:color="#ffd1d1d1" /></shape></item>
</selector>

最后一点了吧,我们在AndroidManifest.xml中需要添加

<activity
            android:name=".AddTagActivity"android:theme="@style/dialogstyle" />

用于我们自定义标签,弹出的一个类似于对话框的一个Activity,这里我们引用了自定义一个样式

<style name="dialogstyle"><!--设置dialog的背景--><item name="android:windowBackground">@android:color/transparent</item><!--设置Dialog的windowFrame框为无--><item name="android:windowFrame">@null</item><!--设置无标题--><item name="android:windowNoTitle">true</item><!--是否浮现在activity之上--><item name="android:windowIsFloating">true</item><!--是否半透明--><item name="android:windowIsTranslucent">true</item><!--设置窗口内容不覆盖--><item name="android:windowContentOverlay">@null</item><!--设置动画,在这里使用让它继承系统的Animation.Dialog--><item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item><!--背景是否模糊显示--><item name="android:backgroundDimEnabled">true</item></style>

对于这个类似于对话框的一个Activity,有不明白的可以上我之前的一篇文章: Android中使用Dialog风格弹出框的Activity

好了,已经全部写完了,有什么疑问的,请在下面留言,有不足的还望指导,感谢各位^_^

源码下载

Android 自定义ViewGroup之实现FlowLayout-标签流容器相关推荐

  1. android 自定义flowlayout,Android 自定义ViewGroup之实现FlowLayout-标签流容器

    本篇文章讲的是Android 自定义ViewGroup之实现标签流式布局-FlowLayout,开发中我们会经常需要实现类似于热门标签等自动换行的流式布局的功能,网上也有很多这样的FlowLayout ...

  2. android自定义viewgroup之我也玩瀑布流

    先看效果图吧, 继上一篇<android自定义viewgroup实现等分格子布局>中实现的布局效果,这里稍微有些区别,每个格子的高度不规则,就是传说的瀑布流布局,一般实现这种效果,要么用第 ...

  3. Android自定义文件路径箭头,Android自定义ViewGroup实现带箭头的圆角矩形菜单

    本文和大家一起做一个带箭头的圆角矩形菜单,大概长下面这个样子: 要求顶上的箭头要对准菜单锚点,菜单项按压反色,菜单背景色和按压色可配置. 最简单的做法就是让UX给个三角形的图片往上一贴,但是转念一想这 ...

  4. Android自定义ViewGroup实现朋友圈九宫格控件

    在我们的实际应用中,经常需要用到自定义控件,比如自定义圆形头像,自定义计步器等等,这篇文章主要给大家介绍了关于Android自定义ViewGroup实现朋友圈九宫格控件的相关资料,需要的朋友可以参考下 ...

  5. Android 自定义ViewGroup 实战篇 - 实现FlowLayout

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38352503 ,本文出自[张鸿洋的博客] 1.概述 上一篇已经基本给大家介绍了如 ...

  6. Android自定义ViewGroup的布局,往往都是从流式布局开始

    前言 前面几篇我们简单的复习了一下自定义 View 的测量与绘制,并且回顾了常见的一些事件的处理方式. 那么如果我们想自定义 ViewGroup 的话,它和自定义View又有什么区别呢?其实我们把 V ...

  7. android 自定义viewgroup onmeasure,一篇文章搞懂Android 自定义Viewgroup的难点

    本文的目的 目的在于教会大家到底如何自定义viewgroup,自定义布局和自定义测量到底如何写.很多网上随便搜搜的概念和流程图这里不再过多描述了,建议大家看本文之前,先看看基本的自定义viewgrou ...

  8. Android自定义ViewGroup基本步骤

    1.自定义属性,获取自定义属性,可参考 ​ Android自定义View基本步骤 ​ 2.onMeasure() 方法,for循环测量子View,根据子View的宽高来计算自己的宽 高 3.onDra ...

  9. android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]

    http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...

最新文章

  1. VSTO之旅系列(三):自定义Excel UI
  2. matlab中select,[转载]MATLAB阈值获取函数ddencmp、thselect、wbmpen和w
  3. 运维自动化之使用PHP+MYSQL+SHELL打造私有监控系统(一)
  4. dbm和mysql使用场景_mysql基本用法总结
  5. go mod导入本地包
  6. oracle强大函数,oracle 函数排行
  7. 前端开发学习之——dom ready和window onload的区别
  8. android轮播图实现方案,Android轮播图实现教程
  9. 设计缓存系统:缓存穿透,缓存击穿,缓存雪崩解决方案分析
  10. mondrain配置mysql_Mondrian + JPivot 环境配置
  11. 《Head First Servlets JSP》-11-Web应用部署
  12. android大智慧安装目录,大智慧文件目录
  13. win10非核心版本的计算机上
  14. VIN码识别,车架号识别,移动端VIN码识别独家支持云识别
  15. JavaScript前端数据呈现——Table会拐弯的column,数据分多列展示
  16. 简析 React Native 用户反馈功能实现
  17. sap 查询数据 未分离版本
  18. 关于字符集的测试报告(转)
  19. 42所双一流高校,都是如何处理学术不端行为的?
  20. mysql rpm怎么用_linux下mysql(rpm)安装使用手册

热门文章

  1. anaconda安装pyqt5
  2. 为什么员工激励总达不到预期的效果?
  3. 接口越权扫描平台初探
  4. MyApps Iscript常用代码
  5. 电子健康档案相关知识问答汇总
  6. 有关于集成开发工具IntelliJ IDEA的安装
  7. 小程序使用echarts-条形统计图
  8. Go安装教程以及解决第三方插件无法安装的问题
  9. 《深入分析GCC 》——2.5 GCC调试选项
  10. 【电子系课设】数字电路课设—知识总结!建议收藏!