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

本篇文章讲的是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) {

        }
    }
}

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

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

时间: 2024-10-12 04:22:21

Android 自定义ViewGroup之实现FlowLayout-标签流容器的相关文章

Android 自定义ViewGroup 实战篇 -&gt; 实现FlowLayout

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38352503 ,本文出自[张鸿洋的博客] 1.概述 上一篇已经基本给大家介绍了如何自定义ViewGroup,如果你还不了解,请查看:Android 手把手教您自定ViewGroup ,本篇将使用上篇介绍的方法,给大家带来一个实例:实现FlowLayout,何为FlowLayout,如果对Java的Swing比较熟悉的话一定不会陌生,就是控件根据ViewGroup的宽,自动的往右

Android 自定义ViewGroup手把手教你实现ArcMenu

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这么个开源项目~~~~当然本篇不是教你如何使用这个开源项目,而是教你如何自己通过自定义ViewGroup写这样的效果,自定义ViewGroup也是我的痛楚,嘿嘿,希望以此可以抛砖引玉~~ 效果图: 1.实现思路 通过效果图,会有几个问题: a.动画效果如何实现 可以看出动画是从顶点外外发射的,可能有人

android自定义viewgroup实现等分格子布局

先上效果图: 实现这样的效果: 一般的思路就是,直接写布局文件,用LinearLayout 嵌套多层子LinearLayout,然后根据权重layout_weight可以达到上面的效果 还有就是利用gridview了,但是这里的需求就是不能上下滑动,使用gridview的时候还要计算布局的高度,否则内容超出下滑: 开始我是用的第一种,直接在布局文件实现了,但是后来发现代码太多太恶心哦,所以我继承viewGroup,重写两个关键的方法:onLayout(),onMeasure() 我的大致思路:

Android自定义ViewGroup (选择照片或者拍照)

教你搞定Android自定义ViewGroup http://www.jianshu.com/p/138b98095778 字数1794 阅读7030 评论8 喜欢37 上一篇我们介绍了Android中自定义View的知识,并实现了一个类似Google彩虹进度条的自定义View,今天我们将进一步学习如何去自定义一个ViewGroup. ViewGroup 我们知道ViewGroup就是View的容器类,我们经常用的LinearLayout,RelativeLayout等都是ViewGroup的子

Android自定义ViewGroup实现流式布局

实现宽度不足自动换行的流式布局: FlowLayout.java package com.jackie.flowlayout; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * Created by Jackie on 8/28/15. */ public class FlowLayout

[Android] 自定义ViewGroup最佳入门实践

对自定义view还不是很了解的码友可以先看自定义View入门这篇文章,本文主要对自定义ViewGroup的过程的梳理,废话不多说. 1.View 绘制流程 ViewGroup也是继承于View,下面看看绘制过程中依次会调用哪些函数. 说明: measure()和onMeasure() 在View.Java源码中: public final void measure(int widthMeasureSpec,int heightMeasureSpec){ ... onMeasure ... } p

android 自定义流布局。实现热门标签。开源库SimpleFlowLayout

前言 实际项目中需要实现一个 热门搜索 的栏目,类似下图: 由于 子项(子view) 中的文字是可变的,一行能显示的 子项 的个数也无法确定.需要支持自动换行和计算位置. 开源类库 我自己写了个 自定义view ,继承自viewGroup, 来实现它,托管到github开源平台. 名称:SimpleFlowLayout 地址:https://github.com/vir56k/SimpleFlowLayout 特点:可以不断添加多个子view,计算位置,自动换行. 类似html中的div标签 适

Android 自定义ViewGroup,实现侧方位滑动菜单

侧方位滑动菜单 1.现在adnroid流行的应用当中很多都是用的侧方位滑动菜单如图: 将菜单显示在左边,内容页面显示在右边,通过滑动或则按钮点击来隐藏和显示菜单. 2.首先对ViewGroup进行个了解: View是ViewGroup的父类,ViewGroup具有View的所有特性,ViewGroup主要用用来充当View的容器,将其中的View作为自己孩子, 并对其进行管理,当然孩子也是可以是ViewGroup类型. View类一般用于绘图操作,重写他的onDraw方法,但它不可以包含其他组件

Android自定义ViewGroup(一)

之前写了两篇关于自定义view的文章,本篇讲讲自定义ViewGroup的实现. 我们知道ViewGroup就是View的容器类,我们经常用的LinearLayout,RelativeLayout等都是ViewGroup的子类.并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性,都是为用于告诉容器的),我们的宽度(layout_width).高度(layout_height).对齐方式(layout_gravity)等:于是乎,ViewGroup的职能为:给childView