时间:2021-05-20
本篇文章讲的是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方法
/** * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高 */ @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<String> mList; public FlowLayoutAdapter(Context context, List<String> 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<String> 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<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; } /** * 回传数据 */ @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) { } }}5、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>6、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" />6、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>7、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
源码下载:http://xiazai.jb51.net/201609/yuanma/AndroidFlowLayout(jb51.net).rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
本篇继续来讲自定义ViewGroup,给大家带来一个实例:FlowLayout。何为FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如
本文实例为大家分享了Android自定义ViewGroup实现流式布局的具体代码,供大家参考,具体内容如下1.概述本篇给大家带来一个实例,FlowLayout,
之前写了两篇关于自定义view的文章,本篇讲讲自定义ViewGroup的实现。我们知道ViewGroup就是View的容器类,我们经常用的LinearLayou
本文实例为大家分享了Android自定义控件ViewGroup实现标签云的具体代码,供大家参考,具体内容如下实现的功能:基本绘制流程:构造函数获取自定义属性on
Android自定义布局竖向的ViewPager的实现效果图:这个自定义控件涉及到的知识点:自定义ViewGroup中onMeasure和onLayout的写法