Android标签流控件的实现

在我们的开发过程中,常常会遇到这样的场景:
我们展示一种物品或者为某一事物添加一些标签。比如说,我们买一件衣服,可以有以下几种标签:杰克琼斯,男士,运动等等。
但我们这时候可能并不知道标签的数量和每个标签的文字,所以,我们在开发过程中,需要实现下面的功能:
我们从服务器端获取标签的信息,然后将其动态的添加到布局中,并且我们能够得到我们选择容器的信息,并将选中的标签重新返回至服务器。
因此,我们必须计算出每个标签(Button)的长度,并且将其与它的容器做比较,如果容器剩余的长度并不足以容纳一个标签的时候,那么就会另起一行,添加标签,就这样周而复始,直到所有的标签添加到容器中。

在这之前,我们先来复习下view的绘制过程:

View 的绘制三步:
1,measure 控件的大小
   调用onMeasure方法——>调用setMeasuredDimension()方法
   // 确定控件的大小
   setMeasuredDimension(width, height);
2,layout    控件的位置   
        调用onLayout方法
        /**
       *     控制viewgroup 里面的孩子 显示的位置 左上角的坐标 (l,t) 右下角的坐标 (r,b)
     */
     protected void onLayout(boolean changed, int l, int t, int r, int b)
3,draw    控件长的样子
onMeasure()---> onLayout()-->onDraw();
view对象是在onstart方法之后的某一个时刻显示出来。 
下面就是view绘制过程的流程图:

当然view的绘制过程很复杂,很多细节需要注意

今天我们要实现的效果是这样的,如图:



对于这样一个功能,我们的思路如下:

对于标签容器TagLayout继承ViewGroup:
维护行的集合
当前行
行的水平间距
行间的垂直间距
onMeasure方法:
1,获取父控件的宽度
2,获取行的宽度
3,获取子孩子的个数
4,测量每个子孩子
5,构造行(确定行的宽度,水平间隙),把孩子添加到行中,把行添加行容器中
6,获取自己的高度,累加行高+垂直间距
7,设置自己的宽高
onLayout方法:
1,计算每一行的marginTop和marginLeft
2, 让每一行自己去摆放自己孩子的位置
对于每一行Line:
维护的控件集合
行的最大宽度
行的使用了的宽度
行的高度
行控件的水平间距
方法1:添加控件:
计算控件的宽度,高度
计算行的使用了的宽度,和行的高度
把控件添加到控件集合中
方法2:判断控件是否可以添加
获取控件的宽度
如果剩余宽度不足则不能添加
方法3:摆放每个孩子
计算每个孩子的left,top,right,bottom的值,调用layout方法

自定义控件TayLayout继承ViewGroup,代码如下:

package com.hdc.flowlayoutdemo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
 * Created by wk on 2016/6/14.
 */
public class TagLayout extends ViewGroup {

    private List<Line> mLines			= new LinkedList<Line>();	// 用来记录布局中有多少个行
    private Line	mCurrentLine;                                   //当前行
    private int  horizontalSpace = 10;                              //控件之间的间隙
    private int verticalSpace = 10;                                   //行之间的间隙

    public TagLayout(Context context) {
        this(context,null);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 清空记录,,,不清空就会掉坑了
        mLines.clear();
        mCurrentLine = null;
        //1,获取自身的宽度
        int width = MeasureSpec.getSize(widthMeasureSpec);

        //2,获取行的宽度
        int maxWidth = width - getPaddingLeft() - getPaddingRight();

        //3,获取孩子
        for(int i = 0;i<getChildCount();i++){

            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE)
            {
                continue;
            }
            //测量孩子的宽高
            measureChild(child,widthMeasureSpec,heightMeasureSpec);
            //将孩子添加到行中,把行添加到集合中
            if(mCurrentLine == null){
                mCurrentLine = new Line(maxWidth,horizontalSpace);
                mCurrentLine.addView(child);
                mLines.add(mCurrentLine);
            }else{
                if(mCurrentLine.canAdd(child)){
                    // 可以加入
                    mCurrentLine.addView(child);
                }else{
                    // 换行
                    mCurrentLine = new Line(maxWidth, horizontalSpace);
                    // 添加到list
                    mLines.add(mCurrentLine);
                    // 添加孩子
                    mCurrentLine.addView(child);
                }
            }
        }
        //获取自己的高度
        int measuredHeight = getPaddingTop() + getPaddingBottom();

        for (int i = 0 ;i<mLines.size();i++){
              measuredHeight += mLines.get(i).height;  
        }
        measuredHeight = measuredHeight + (mLines.size()-1)*verticalSpace;
        // 设置自己的宽度和高度
        setMeasuredDimension(width,measuredHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int marginTop = getPaddingTop();
        int marginLeft = getPaddingLeft();
        // 让line自己去摆放
        for(int i = 0;i<mLines.size();i++){
            Line line = mLines.get(i);
            line.layout(marginLeft,marginTop);
            marginTop += verticalSpace + line.height;
        }
    }
    /**
     * 用来记录每行控件的摆放
      */
    private class Line{
        private List<View> mViews = new ArrayList<View>();
        private int maxWidth ;                              //行的宽度
        private int space;                                  //行的水平间隙
        private int usedWidth ;                             //行使用了的宽度
        public int height;                                  //行高

        public Line(int maxWidth, int horizontalSpace) {
            this.maxWidth = maxWidth;
            this.space = horizontalSpace;
        }

        /**
         * 添加子控件
         * @param child
         */
        public void addView(View child) {
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            if(mViews.size()==0){
                // 如果没有控件的情况下
                if(childWidth > maxWidth){
                    usedWidth = maxWidth;
                    height = childHeight;
                }else{
                    usedWidth = childWidth;
                    height = childHeight;
                }
            }else{
                //有控件
                usedWidth = usedWidth + space+ childWidth;
                height = (childHeight>height)?childHeight:height;
            }
            mViews.add(child);
        }

        /**
         * 判断是否可以添加子控件
         * @param child
         * @return
         */
        public boolean canAdd(View child) {
             int width = child.getMeasuredWidth();
            // line中没有view时
            if (mViews.size() == 0)
            {
                // 只要没有,就可以加
                return true;
            }
            if(usedWidth + width + space > maxWidth){
                return false;
            }else{
                return true;
            }
        }

        /**
         * 摆放行
         * @param marginLeft
         * @param marginTop
         */
        public void layout(int marginLeft, int marginTop) {
            int extraWidth = maxWidth - usedWidth;
            int avgWidth = (int)(extraWidth*1f/mViews.size()+0.5f);
            //计算控件的上下左右的位置
            for(int i=0;i<mViews.size();i++){

                View view = mViews.get(i);

                int viewWidth = view.getMeasuredWidth();
                int viewHeight = view.getMeasuredHeight();

                if(avgWidth > 0){
                    // 重新的去期望孩子的宽高
                    int specWidth = MeasureSpec.makeMeasureSpec(viewWidth + avgWidth,MeasureSpec.EXACTLY);
                    int specHeight = MeasureSpec.makeMeasureSpec(viewHeight,MeasureSpec.EXACTLY);
                    view.measure(specWidth,specHeight);
                    // 重新获取宽度和高度
                    viewWidth = view.getMeasuredWidth();
                    viewHeight = view.getMeasuredHeight();
                }

                int extraTop =(int) ((height - viewHeight)/2f + 0.5);

                int left = marginLeft;
                int top = marginTop + extraTop;
                int right = left + viewWidth;
                int bottom = top + viewHeight;
                //摆放每一个孩子的位置
                view.layout(left,top,right,bottom);
                marginLeft += viewWidth + space;
            }
        }
    }
}
主界面布局文件,activity_main.xml

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <com.hdc.flowlayoutdemo.TagLayout
        android:id="@+id/flowlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</ScrollView>
主界面调用很简单,MainActivity.java

package com.hdc.flowlayoutdemo;
import java.util.Random;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
import android.view.Gravity;
import android.view.Menu;
import android.widget.TextView;
public class MainActivity extends Activity
{
	private String[]	mDatas	= new String[] { "QQ", "视频", "放开那三国", "电子书", "酒店",
								"单机", "小说", "斗地主", "优酷", "网游", "WIFI万能钥匙", "播放器", "捕鱼达人2", "机票",
								"游戏", "熊出没之熊大快跑", "美图秀秀", "浏览器", "单机游戏", "我的世界", "电影电视", "QQ空间",
								"旅游", "免费游戏", "2048", "刀塔传奇", "壁纸", "节奏大师", "锁屏", "装机必备", "天天动听",
								"备份", "网盘", "海淘网", "大众点评", "爱奇艺视频", "腾讯手机管家", "百度地图", "猎豹清理大师",
								"谷歌地图", "hao123上网导航", "京东", "youni有你", "万年历-农历黄历", "支付宝钱包" };

	private TagLayout	mLayout;
	private int[] colors = {Color.GRAY,Color.LTGRAY,Color.RED,Color.YELLOW,Color.BLUE};
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mLayout = (TagLayout) findViewById(R.id.flowlayout);
		mLayout.setPadding(10, 10, 10, 10);
		// 动态加载数据
		Random rdm = new Random();
		for (int i = 0; i < mDatas.length; i++)
		{
			TextView view = new TextView(this);
			view.setText(mDatas[i]);
			view.setBackgroundColor(colors[rdm.nextInt(5)]);
			view.setTextColor(Color.WHITE);
			view.setPadding(5, 5, 5, 5);
			view.setGravity(Gravity.CENTER);
			view.setTextSize(rdm.nextInt(10) + 16);
			// view.setTextSize(15);
			mLayout.addView(view);
		}
	}
}

到这里已经完成,如需源码,点击 这里,欢迎留言。。。






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tag的使用 package com.yarin.android.qiehuan; import android.app.AlertDialog; import android.app.Dialog; import android.app.TabActivity; import android.content.DialogInterface; import android.graphics.Color; import android.os.Bundle; import android.widget.TabHost; import android.widget.TabHost.OnTabChangeListener; public class Activity01 extends TabActivity { //声明TabHost对象 TabHost mTabHost; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //取得TabHost对象 mTabHost = getTabHost(); /* 为TabHost添加标签 */ //新建一个newTabSpec(newTabSpec) //设置其标签和图标(setIndicator) //设置内容(setContent) mTabHost.addTab(mTabHost.newTabSpec("test1") .setIndicator("TAB 1",getResources().getDrawable(R.drawable.img1)) .setContent(R.id.textview1)); mTabHost.addTab(mTabHost.newTabSpec("test2") .setIndicator("TAB 2",getResources().getDrawable(R.drawable.img2)) .setContent(R.id.textview2)); mTabHost.addTab(mTabHost.newTabSpec("test3") .setIndicator("TAB 3",getResources().getDrawable(R.drawable.img3)) .setContent(R.id.textview3)); //设置TabHost的背景颜色 mTabHost.setBackgroundColor(Color.argb(150, 22, 70, 150)); //设置TabHost的背景图片资源 //mTabHost.setBackgroundResource(R.drawable.bg0); //设置当前显示哪一个标签 mTabHost.setCurrentTab(0); //标签切换事件处理,setOnTabChangedListener mTabHost.setOnTabChangedListener(new OnTabChangeListener() { // TODO Auto-generated method stub @Override public void onTabChanged(String tabId) { Dialog dialog = new AlertDialog.Builder(Activity01.this) .setTitle("善谢谢提醒") .setMessage("现在选中了:"+tabId+"标签") .setPositiveButton("确定", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { dialog.cancel(); } }).create();//创建按钮 dialog.show(); } }); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值