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

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

这里写图片描述

一、写代码之前,有几个是问题是我们先要弄清楚的:

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 , 在里面定义我们的需要用到的属性以及声明相对应属性的取值类型

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

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

xmlns:custom="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

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" />

android:layout_width="match_parent"

android:layout_height="wrap_content">

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical">

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" />

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" />

android:layout_width="match_parent"

android:layout_height="10dp"

android:background="#f6f6f6" />

android:layout_width="match_parent"

android:layout_height="46dp"

android:background="@android:color/white">

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_centerVertical="true"

android:paddingLeft="15dp"

android:text="推荐标签 "

android:textSize="16dp" />

android:layout_width="match_parent"

android:layout_height="1dp"

android:background="#f6f6f6" />

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" />

一定要引入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方法

/**

* 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高

*/

@Override

protected 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的数目,遍历循环出每个子View

for (int i = 0; i < getChildCount(); i++) {

//拿到index上的子view

View 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();// 重新开启新行,开始记录childLeft

childTop += 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方法

@Override

protected 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();

}

/**

* 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高

*/

@Override

protected 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的数目,遍历循环出每个子View

for (int i = 0; i < getChildCount(); i++) {

//拿到index上的子view

View 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();// 重新开启新行,开始记录childLeft

childTop += mVerticalSpacing + childHeight;// 叠加当前的高度

lineHeight = childHeight;// 开启记录下一行的高度

}else{

//否则累加当前childView的宽度

childLeft += childWidth + mHorizontalSpacing;

}

}

height += childTop + lineHeight + getPaddingBottom();

setMeasuredDimension(widthSize, heighMode == MeasureSpec.EXACTLY ? heighSize : height);

}

@Override

protected 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() {

@Override

public 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 {

@Override

public void onChanged() {

drawLayout();

}

@Override

public 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 mList;

public FlowLayoutAdapter(Context context, List list) {

mContext = context;

mList = list;

}

@Override

public int getCount() {

return mList.size();

}

@Override

public String getItem(int position) {

return mList.get(position);

}

@Override

public long getItemId(int position) {

return position;

}

@Override

public 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 MyLabelLists, HotLabelLists;

private static int TAG_REQUESTCODE = 0x101;

@Override

protected 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;

}

@Override

public 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 str, String compareStr) {

Boolean isExist = false;//默认沒有相同标签

for (int i = 0; i < str.size(); i++) {

if (compareStr.equals(str.get(i))) {

isExist = true;

}

}

return isExist;

}

/**

* 回传数据

*/

@Override

protected 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;

@Override

protected 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);

}

@Override

public 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 {

@Override

public void beforeTextChanged(CharSequence s, int start, int count,

int after) {

}

@Override

public 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();

}

}

@Override

public void afterTextChanged(Editable s) {

}

}

}

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

美妆

画板

漫画

高科技

韩国电影

股票

美术

高富帅

鸿泰安

运动

外语

财经

大叔

非主流

暴走漫画

心理学

汉语

白富美

自定义

7、item_tag.xml

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

android:layout_width="250dp"

android:layout_height="wrap_content"

android:background="@android:color/white"

android:gravity="center_horizontal"

android:orientation="vertical"

android:padding="5dp" >

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical" >

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginLeft="5dp"

android:text="请输入想要添加的标签"

android:textColor="@android:color/black"

android:textSize="16dp" />

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" />

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" />

9、selector_btn_item.xml

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

android:name=".AddTagActivity"

android:theme="@style/dialogstyle" />

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

@android:color/transparent

@null

true

true

true

@null

@android:style/Animation.Dialog

true

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

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

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

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

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

  2. android 自定义图片容器,Android应用开发中自定义ViewGroup视图容器的教程

    一.概述在写代码之前,我必须得问几个问题: 1.ViewGroup的职责是啥?ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性, ...

  3. 【Android 应用开发】自定义View 和 ViewGroup

    一. 自定义View介绍 自定义View时, 继承View基类, 并实现其中的一些方法. (1) ~ (2) 方法与构造相关 (3) ~ (5) 方法与组件大小位置相关 (6) ~ (9) 方法与触摸 ...

  4. Android 手把手教您自定义ViewGroup

    最近由于工作的变动,导致的博客的更新计划有点被打乱,希望可以尽快脉动回来~ 今天给大家带来一篇自定义ViewGroup的教程,说白了,就是教大家如何自定义ViewGroup,如果你对自定义ViewGr ...

  5. Android开发实践:自定义ViewGroup的onLayout()分析

    Android开发中,对于自定义View,分为两种,一种是自定义控件(继承View类),另一种是自定义布局容器(继承ViewGroup).如果是自定义控件,则一般需要重载两个方法,一个是onMeasu ...

  6. android自动标齐,自定义FlowLayout,支持多种布局优化--android-flowlayout

    前言 flow layout, 流式布局, 这个概念在移动端或者前端开发中很常见,特别是在多标签的展示中, 往往起到了关键的作用.然而Android 官方, 并没有为开发者提供这样一个布局, 于是有很 ...

  7. Android动画效果之自定义ViewGroup添加布局动画

    Android动画效果之自定义ViewGroup添加布局动画 前言: 前面几篇文章介绍了补间动画.逐帧动画.属性动画,大部分都是针对View来实现的动画,那么该如何为了一个ViewGroup添加动画呢 ...

  8. android 自定义控件央视,Android自定义ViewGroup之第一次接触ViewGroup

    整理总结自鸿洋的博客:http://blog.csdn.net/lmj623565791/article/details/38339817/ 一.com.cctvjiatao.customviewgr ...

  9. android view上下滚动条,Android自定义View六(ViewGroup水平垂直滚动实现类似支付宝年度账单的效果)...

    先看两张效果图 1.垂直滑动 onegif.gif 2.水平滑动 twoGIF.gif 先看使用方法 1.AndroidStudio 引入 Project.gradle repositories { ...

最新文章

  1. 面试:说说 Spring MVC 的执行过程?
  2. Jmeter常用插件——梯度加压、响应时间、TPS_老版本
  3. 取出客户端卸载CKEditor中的内容,放入数据库中的方案
  4. c# 结构体 4字节对齐_C语言程序员们常说的“内存对齐”,究竟有什么目的?
  5. python--文件
  6. UE4删除C++Classes下的类
  7. 教你chrome浏览器断点调试理解闭包
  8. PDF怎么编辑修改内容
  9. 基于MATLAB的有源三相滤波器的设计,基于MATLAB的有源滤波器的设计与仿真
  10. matlab 频散曲线,Matlab绘制频散曲线程序代码
  11. 数字滤波算法——程序判断滤波
  12. mac下安装photoshop
  13. Kindle资源-史上最全60GB的Kindle电子书资源网盘打包下载
  14. 从图形到像素:前端图形编程技术概览
  15. python html文本转为text文本
  16. centos安装easy_install
  17. idea突然打不开了【已解决】
  18. Scrapy抓取西刺高匿代理ip
  19. 单片机c语言中延时函数的作用,单片机中C语言延时函数
  20. linux配置网卡设DDNS,Linux之配置安全的DDNS实例

热门文章

  1. RTMPdump(libRTMP)源代码分析 4: 连接第一步——握手(Hand Shake)
  2. MySql 5.7.19 源代码安装 for ubuntu 16.04
  3. 如何在eclipse中查看源码
  4. HiveQL基本操作整理
  5. Google Spanner:谷歌的全球分布式数据库
  6. poj 2352 线段树
  7. hdu 1392 Surround the Trees
  8. Leecode01. 两数之和——Leecode大厂热题100道系列
  9. linux-任务调度
  10. Python程序开发——第十章 正则表达式(ヾ(•ω•`)o那么复杂的正则表达式看完这一篇就懂啦)