Android自动换行标签

原创 2015年11月19日 15:17:22

总结了一下Android中如何实现自动换行的LinearLayout。

在本文中,说是LinearLayout其实是继承自GroupView,在这里主要重写了两个方法,onMeasure、onLayout方法,下面我对此加以介绍。(代码中使用了AttributeSet,由于时间问题不再予以介绍)。

1.     onMeasure是干什么的?

在ViewGroup的创建过程中,onMeasure是在onLayout之前的,所以在此先对onMeasure进行介绍,onMeasure方法是计算子控件与父控件在屏幕中所占长宽大小的,onMeasure传入两个参数——widthMeasureSpec和heightMeasureSpec. 这两个参数指明控件可获得的空间以及关于这个空间描述的元数据.

int withMode = MeasureSpec.getMode(widthMeasureSpec);
int withSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

Mode有3种模式分别是UNSPECIFIED, EXACTLY和AT_MOST,如果是AT_MOST,Size代表的是最大可获得的空间;如果是EXACTLY,Size代表的是精确的尺寸;如果是UNSPECIFIED,就是你想要多少就有多少。经过代码测试就知道,当我们设置width或height为fill_parent时,容器在布局时调用子 view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。而当设置为 wrap_content时,容器传进去的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY。

2.     onLayout是干什么的?

与onMesaure相比,onLayout更加容易理解,它的作用就是调座位,就是把所有的子View根据不同的需要,通过View. layout(int l, int t, int r, int b)方法指定它所在的位置。

3.     解决问题

只要对onMeasure和onLayout加以理解,对于该篇所要实现的功能就不再难以实现,下面贴上代码,并在代码中讲解。

WaroLinearLayout.java

[java] view plaincopy
  1. public class WarpLinearLayout extends ViewGroup {  
  2.   
  3.     private Type mType;  
  4.     private List<WarpLine> mWarpLineGroup;  
  5.   
  6.     public WarpLinearLayout(Context context) {  
  7.         this(context, null);  
  8.     }  
  9.   
  10.     public WarpLinearLayout(Context context, AttributeSet attrs) {  
  11.         this(context, attrs, R.style.WarpLinearLayoutDefault);  
  12.     }  
  13.   
  14.     public WarpLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {  
  15.         super(context, attrs, defStyleAttr);  
  16.         mType = new Type(context, attrs);  
  17.     }  
  18.   
  19.     @Override  
  20.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  21.         int withMode = MeasureSpec.getMode(widthMeasureSpec);  
  22.         int withSize = MeasureSpec.getSize(widthMeasureSpec);  
  23.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  24.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  25.         int with = 0;  
  26.         int height = 0;  
  27.         int childCount = getChildCount();  
  28.         /** 
  29.          * 在调用childView。getMeasre之前必须先调用该行代码,用于对子View大小的测量 
  30.          */  
  31.         measureChildren(widthMeasureSpec, heightMeasureSpec);  
  32.         /** 
  33.          * 计算宽度 
  34.          */  
  35.         switch (withMode) {  
  36.             case MeasureSpec.EXACTLY:  
  37.                 with = withSize;  
  38.                 break;  
  39.             case MeasureSpec.AT_MOST:  
  40.                 for (int i = 0; i < childCount; i++) {  
  41.                     if (i != 0) {  
  42.                         with += mType.horizontal_Space;  
  43.                     }  
  44.                     with += getChildAt(i).getMeasuredWidth();  
  45.                 }  
  46.                 with += getPaddingLeft() + getPaddingRight();  
  47.                 with = with > withSize ? withSize : with;  
  48.                 break;  
  49.             case MeasureSpec.UNSPECIFIED:  
  50.                 for (int i = 0; i < childCount; i++) {  
  51.                     if (i != 0) {  
  52.                         with += mType.horizontal_Space;  
  53.                     }  
  54.                     with += getChildAt(i).getMeasuredWidth();  
  55.                 }  
  56.                 with += getPaddingLeft() + getPaddingRight();  
  57.                 break;  
  58.             default:  
  59.                 with = withSize;  
  60.                 break;  
  61.   
  62.         }  
  63.         /** 
  64.          * 根据计算出的宽度,计算出所需要的行数 
  65.          */  
  66.         WarpLine warpLine = new WarpLine();  
  67.         /** 
  68.          * 不能够在定义属性时初始化,因为onMeasure方法会多次调用 
  69.          */  
  70.         mWarpLineGroup = new ArrayList<WarpLine>();  
  71.         for (int i = 0; i < childCount; i++) {  
  72.             if (warpLine.lineWidth + getChildAt(i).getMeasuredWidth() + mType.horizontal_Space > with) {  
  73.                 if (warpLine.lineView.size() == 0) {  
  74.                     warpLine.addView(getChildAt(i));  
  75.                     mWarpLineGroup.add(warpLine);  
  76.                     warpLine = new WarpLine();  
  77.                 } else {  
  78.                     mWarpLineGroup.add(warpLine);  
  79.                     warpLine = new WarpLine();  
  80.                     warpLine.addView(getChildAt(i));  
  81.                 }  
  82.             } else {  
  83.                 warpLine.addView(getChildAt(i));  
  84.             }  
  85.         }  
  86.         /** 
  87.          * 添加最后一行 
  88.          */  
  89.         if (warpLine.lineView.size() > 0 && !mWarpLineGroup.contains(warpLine)) {  
  90.             mWarpLineGroup.add(warpLine);  
  91.         }  
  92.         /** 
  93.          * 计算宽度 
  94.          */  
  95.         height = getPaddingTop() + getPaddingBottom();  
  96.         for (int i = 0; i < mWarpLineGroup.size(); i++) {  
  97.             if (i != 0) {  
  98.                 height += mType.vertical_Space;  
  99.             }  
  100.             height += mWarpLineGroup.get(i).height;  
  101.         }  
  102.         switch (heightMode) {  
  103.             case MeasureSpec.EXACTLY:  
  104.                 height = heightSize;  
  105.                 break;  
  106.             case MeasureSpec.AT_MOST:  
  107.                 height = height > heightSize ? heightSize : height;  
  108.                 break;  
  109.             case MeasureSpec.UNSPECIFIED:  
  110.                 break;  
  111.             default:  
  112.                 break;  
  113.         }  
  114.         setMeasuredDimension(with, height);  
  115.     }  
  116.   
  117.     @Override  
  118.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  119.         t = getPaddingTop();  
  120.         for (int i = 0; i < mWarpLineGroup.size(); i++) {  
  121.             int left = getPaddingLeft();  
  122.             WarpLine warpLine = mWarpLineGroup.get(i);  
  123.             int lastWidth = getMeasuredWidth() - warpLine.lineWidth;  
  124.             for (int j = 0; j < warpLine.lineView.size(); j++) {  
  125.                 View view = warpLine.lineView.get(j);  
  126.                 if (isFull()) {//需要充满当前行时  
  127.                     view.layout(left, t, left + view.getMeasuredWidth() + lastWidth / warpLine.lineView.size(), t + view.getMeasuredHeight());  
  128.                     left += view.getMeasuredWidth() + mType.horizontal_Space + lastWidth / warpLine.lineView.size();  
  129.                 } else {  
  130.                     switch (getGrivate()) {  
  131.                         case 0://右对齐  
  132.                             view.layout(left + lastWidth, t, left + lastWidth + view.getMeasuredWidth(), t + view.getMeasuredHeight());  
  133.                             break;  
  134.                         case 2://居中对齐  
  135.                             view.layout(left + lastWidth / 2, t, left + lastWidth / 2 + view.getMeasuredWidth(), t + view.getMeasuredHeight());  
  136.                             break;  
  137.                         default://左对齐  
  138.                             view.layout(left, t, left + view.getMeasuredWidth(), t + view.getMeasuredHeight());  
  139.                             break;  
  140.                     }  
  141.                     left += view.getMeasuredWidth() + mType.horizontal_Space;  
  142.                 }  
  143.             }  
  144.             t += warpLine.height + mType.vertical_Space;  
  145.         }  
  146.     }  
  147.   
  148.     /** 
  149.      * 用于存放一行子View 
  150.      */  
  151.     private final class WarpLine {  
  152.         private List<View> lineView = new ArrayList<View>();  
  153.         /** 
  154.          * 当前行中所需要占用的宽度 
  155.          */  
  156.         private int lineWidth = getPaddingLeft() + getPaddingRight();  
  157.         /** 
  158.          * 该行View中所需要占用的最大高度 
  159.          */  
  160.         private int height = 0;  
  161.   
  162.         private void addView(View view) {  
  163.             if (lineView.size() != 0) {  
  164.                 lineWidth += mType.horizontal_Space;  
  165.             }  
  166.             height = height > view.getMeasuredHeight() ? height : view.getMeasuredHeight();  
  167.             lineWidth += view.getMeasuredWidth();  
  168.             lineView.add(view);  
  169.         }  
  170.     }  
  171.   
  172.     /** 
  173.      * 对样式的初始化 
  174.      */  
  175.     private final static class Type {  
  176.         /* 
  177.          *对齐方式 right 0,left 1,center 2 
  178.         */  
  179.         private int grivate;  
  180.         /** 
  181.          * 水平间距,单位px 
  182.          */  
  183.         private float horizontal_Space;  
  184.         /** 
  185.          * 垂直间距,单位px 
  186.          */  
  187.         private float vertical_Space;  
  188.         /** 
  189.          * 是否自动填满 
  190.          */  
  191.         private boolean isFull;  
  192.   
  193.         Type(Context context, AttributeSet attrs) {  
  194.             if (attrs == null) {  
  195.                 return;  
  196.             }  
  197.             TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WarpLinearLayout);  
  198.             grivate = typedArray.getInt(R.styleable.WarpLinearLayout_grivate, grivate);  
  199.             horizontal_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_horizontal_Space, horizontal_Space);  
  200.             vertical_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_vertical_Space, vertical_Space);  
  201.             isFull = typedArray.getBoolean(R.styleable.WarpLinearLayout_isFull, isFull);  
  202.         }  
  203.     }  
  204.   
  205.     public int getGrivate() {  
  206.         return mType.grivate;  
  207.     }  
  208.   
  209.     public float getHorizontal_Space() {  
  210.         return mType.horizontal_Space;  
  211.     }  
  212.   
  213.     public float getVertical_Space() {  
  214.         return mType.vertical_Space;  
  215.     }  
  216.   
  217.     public boolean isFull() {  
  218.         return mType.isFull;  
  219.     }  
  220.   
  221.     public void setGrivate(int grivate) {  
  222.         mType.grivate = grivate;  
  223.     }  
  224.   
  225.     public void setHorizontal_Space(float horizontal_Space) {  
  226.         mType.horizontal_Space = horizontal_Space;  
  227.     }  
  228.   
  229.     public void setVertical_Space(float vertical_Space) {  
  230.         mType.vertical_Space = vertical_Space;  
  231.     }  
  232.   
  233.     public void setIsFull(boolean isFull) {  
  234.         mType.isFull = isFull;  
  235.     }  
  236.   
  237.     /** 
  238.      * 每行子View的对齐方式 
  239.      */  
  240.     public final static class Gravite {  
  241.         public final static int RIGHT = 0;  
  242.         public final static int LEFT = 1;  
  243.         public final static int CENTER = 2;  
  244.     }  
  245. }  
attrs.xml

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <declare-styleable name="WarpLinearLayout">  
  4.         <attr name="grivate" format="enum"><!--对齐方式 !-->  
  5.             <enum name="right" value="0"></enum>  
  6.             <enum name="left" value="1"></enum>  
  7.             <enum name="center" value="2"></enum>  
  8.         </attr>  
  9.         <attr name="horizontal_Space" format="dimension"></attr>  
  10.         <attr name="vertical_Space" format="dimension"></attr>  
  11.         <attr name="isFull" format="boolean"></attr>  
  12.     </declare-styleable>  
  13. </resources>  

WarpLinearLayoutDefault
[html] view plaincopy
  1. <style name="WarpLinearLayoutDefault">  
  2.        <item name="grivate">left</item>  
  3.        <item name="horizontal_Space">20dp</item>  
  4.        <item name="vertical_Space">20dp</item>  
  5.        <item name="isFull">false</item>  
  6.    </style>  

MainActivity.java

[java] view plaincopy
  1. public class MainActivity extends Activity {  
  2.     private Button btn;  
  3.     private WarpLinearLayout warpLinearLayout;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         btn = (Button) findViewById(R.id.btn);  
  10.         warpLinearLayout = (WarpLinearLayout) findViewById(R.id.warpLinearLayout);  
  11.         btn.setOnClickListener(new View.OnClickListener() {  
  12.             @Override  
  13.             public void onClick(View v) {  
  14.                 int n = new Random().nextInt(10) + 5;  
  15.                 StringBuffer stringBuffer = new StringBuffer();  
  16.                 Random random = new Random();  
  17.                 Log.i("WarpLinearLayout","n="+n);  
  18.                 for (int i = 0; i < n; i++) {  
  19.                     stringBuffer.append((char)(65+random.nextInt(26)));  
  20.                     Log.i("WarpLinearLayout""StringBuffer=" + stringBuffer.toString());  
  21.                 }  
  22.                 TextView tv = new TextView(MainActivity.this);  
  23.                 tv.setText(stringBuffer.toString()+"000");  
  24.                 tv.setBackgroundResource(R.drawable.radius_backgroup_yellow);  
  25.                 tv.setPadding(10,10,10,10);  
  26.                 warpLinearLayout.addView(tv);  
  27.             }  
  28.         });  
  29.     }  
  30. }  
activity_main.xml

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"  
  4.     xmlns:tools="http://schemas.android.com/tools"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     android:paddingBottom="@dimen/activity_vertical_margin"  
  8.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  9.     android:paddingRight="@dimen/activity_horizontal_margin"  
  10.     android:paddingTop="@dimen/activity_vertical_margin"  
  11.     tools:context=".MainActivity">  
  12.   
  13.     <Button  
  14.         android:id="@+id/btn"  
  15.         android:layout_width="match_parent"  
  16.         android:layout_height="wrap_content"  
  17.         android:gravity="center"  
  18.         android:text="add"  
  19.         android:textSize="20dp" />  
  20.   
  21.     <com.example.customview.viewgroup.WarpLinearLayout  
  22.         android:id="@+id/warpLinearLayout"  
  23.         android:layout_width="match_parent"  
  24.         android:layout_height="wrap_content"  
  25.         android:layout_below="@id/btn"  
  26.         android:background="#FF00FF00"  
  27.         android:padding="10dp"  
  28.         app:grivate="right"  
  29.         app:horizontal_Space="10dp"  
  30.         app:isFull="false"  
  31.         app:vertical_Space="10dp"></com.example.customview.viewgroup.WarpLinearLayout>  
  32. </RelativeLayout>  

运行效果图如下:




相关文章推荐

Android根据标签长度自动换行

我们在APP中经常看到这样的效果: 这是美团的热门搜索界面,里面罗列出了长度不等的标签,应用会根据标签的长度自动换行,比如第一行有3个标签,而第二行只有2个标签,这篇文章就来讲下如何实现这种效果,...
  • gesanri
  • gesanri
  • 2015年10月08日 09:38
  • 2367

Android LinearLayout中TextView标签项自动换行的一种解决方案

在一些项目中,比如搜索,广告热门关键词dengd

Android自定义View(LineBreakLayout-自动换行的标签容器)

最近一段时间比较忙,都没有时间更新博客,今天公司的事情忙完得空,继续为我的自定义控件系列博客添砖加瓦。本篇博客讲解的是标签自动换行的布局容器,正好前一阵子有个项目中需要,想了想没什么难度就自己弄了。而...

Android根据标签长度自动换行

  • 2015年10月08日 10:53
  • 5.2MB
  • 下载

Android自定义控件实现标签的显示自动换行(一)

通过控件TagListView实现父控件,然后向里面添加子空间TagView

Android 中自动换行的标签实现

可以自动换行的标签
  • TianFB
  • TianFB
  • 2017年08月03日 18:03
  • 149

Android自动换行标签控件

  • 2016年08月03日 20:09
  • 36KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android自动换行标签
举报原因:
原因补充:

(最多只允许输入30个字)