流式布局的实现

[java]  view plain  copy
  1. public class FlowLayoutView extends ViewGroup {  
  2.     public FlowLayoutView(Context context) {  
  3.         this(context, null);  
  4.     }  
  5.   
  6.     public FlowLayoutView(Context context, AttributeSet attrs) {  
  7.         this(context, attrs,0);  
  8.     }  
  9.   
  10.     public FlowLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {  
  11.         super(context, attrs, defStyleAttr);  
  12.     }  
  13.   
  14.     @Override  
  15.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  16.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  17.     }  
  18.   
  19.     @Override  
  20.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  21.           
  22.     }  
  23. }  



二、在布局文件中使用我们的控件

[html]  view plain  copy
  1. <com.androidlongs.flowlayoutviewapplication.FlowLayoutView  
  2.     android:id="@+id/flowlayout"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent">  
  5. </com.androidlongs.flowlayoutviewapplication.FlowLayoutView>  

三、在 activity中向我们的控件中添加子view



四、定义一行对象,来保存每行中所包含的控件信息

[java]  view plain  copy
  1. class Line{  
  2.         //用来记录当前行的所有TextView  
  3.         private ArrayList<View> viewList = new ArrayList<View>();  
  4.         //表示当前行所有TextView的宽,还有他们之间的水平间距  
  5.         private int width;  
  6.         //当前行的高度  
  7.         private int height;  
  8.           
  9.         }  
  10. }  

五、 在onMeasure方法中完成测量分行操作


[java]  view plain  copy
  1. @Override  
  2.    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  4.   
  5.        //1.获取FlowLayout的宽度  
  6.        int width = MeasureSpec.getSize(widthMeasureSpec);  
  7.        //2.计算用于实际比较的宽度,就是width减去左右的padding值  
  8.        int noPaddingWidth = width - getPaddingLeft()- getPaddingRight();  
  9.        //3.遍历所有的子TextView,在遍历过程中进行比较,进行分行操作  
  10.        //创建一个行  
  11.        Line  line = new Line();  
  12.        for (int i = 0; i < getChildCount(); i++) {  
  13.            final View childAt = getChildAt(i);  
  14.            //1. 当 当前的行中没有子View,那么直接将子childAt直接放入行中,而不用再比较宽度,要保证  
  15.            if(line.getViewList().size()==0){  
  16.                line.addLineView(childAt);  
  17.            }  
  18.            // 每行至少有一个子TextView  
  19.           
  20.        }  
  21.    }  

可以看到这里有一个addLineView方法,我们需要在Line中定义addLineView这个方法

Line对象的addLineView方法简析

1.当我们将view添加到Line去,我们就需要将子View添加到保存子View的集合(viewList)中去,并且要更新Line的width,
    
2.那么在更新width的过程中,如果当前添加的子View是当前行中的第一个子控件,那么当前子View的宽就是当前行的宽
    如果不是第一个,则要在当前width的基础上+水平间距+当前添加子View的宽度
3.在这里使用到和水平间距,所以需要定义当前行中的每个子View的水平间距,


[html]  view plain  copy
  1. private final int DEFAULT_SPACING = 10;  
  2.         private int horizontalSpacing = DEFAULT_SPACING;//水平间距  

4. 通过自定义属性的方式来实现设定水平间距

1. 在values文件夹下新建attrs.xml文件

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2.                     <resources>  
  3.                         <attr name="horizontalSpacing" format="integer"/>  
  4.                         <declare-styleable name="FlowLayoutView">  
  5.                             <attr name="horizontalSpacing"/>  
  6.                         </declare-styleable>  
  7.                     </resources>  

2. 在自定义FlowLayoutView的构造方法中获取我们所设定的水平间距

[html]  view plain  copy
  1. public FlowLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {  
  2.         super(context, attrs, defStyleAttr);  
  3.         //获取所有和自定义属性和样式  
  4.         final TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlowLayoutView, defStyleAttr, 0);  
  5.         final int indexCount = typedArray.getIndexCount();  
  6.         for (int i = 0; i < indexCount; i++) {  
  7.             final int indexAttr = typedArray.getIndex(i);  
  8.             switch (indexAttr) {  
  9.             case R.styleable.FlowLayoutView_horizontalSpacing:  
  10.                 horizontalSpacing = typedArray.getInt(indexAttr,10);  
  11.                 break;  
  12.                      }  
  13.                 }  
  14.             typedArray.recycle();  
  15.         }  


5. 通过向外暴露一个方法来设置

[java]  view plain  copy
  1. /** 
  2.  * 设置子View直接的水平间距 
  3.  * @param horizontalSpacing 
  4. */  
  5. public void setHorizontalSpacing(int horizontalSpacing){  
  6.         if(horizontalSpacing>0){  
  7.         this.horizontalSpacing = horizontalSpacing;  
  8.         }  
  9.     }  

6. 添加子view后,更新当前行的高度,实际上在这里,每个子View的高度就是当前行的高度

[java]  view plain  copy
  1. public void addLineView(View lineView){  
  2.             if(!viewList.contains(lineView)){  
  3.                 viewList.add(lineView);  
  4.                   
  5.                 //更新width  
  6.                 if(viewList.size()==1){  
  7.                     //如果是第一个TextView,那么width就是lineView的宽度  
  8.                     width = lineView.getMeasuredWidth();  
  9.                 }else {  
  10.                     //如果不是第一个,则要在当前width的基础上+水平间距+lineView的宽度  
  11.                     width += horizontalSpacing + lineView.getMeasuredWidth();  
  12.                 }  
  13.                 //更新height,在此所有的TextView的高度都是一样的  
  14.                 height = Math.max(height,lineView.getMeasuredHeight());  
  15.             }  
  16.             }  

分行逻辑简析

  当当前行中已经有了子view后,当前行的宽度+水平间距+当前添加的子View的宽度大于控件的宽度的时候,需要进行换行操作,那么在换行操作之前,需要先保存之前的行,所以这里使用集合来进行保存


[html]  view plain  copy
  1. private ArrayList<Line> lineList = new ArrayList<FlowLayout.Line>();  

  当前行的宽度+水平间距+当前添加的子View的宽度不大于控件的宽度的时候,直接将当前子View添加到当前行中去

[java]  view plain  copy
  1. //遍历所有的子TextView,进行分行操作  
  2.         Line line = new Line();//只要不换行,始终都是同一个Line对象  
  3.         for (int i = 0; i<getChildCount(); i++) {  
  4.         //获取子TextView  
  5.             View childView = getChildAt(i);  
  6.             //引起view的onMeasure方法回调,从而保证后面的方法能够有值  
  7.             childView.measure(0,0);  
  8.               
  9.             //如果当前line中 没有TextView,则直接放入当前Line中  
  10.             if(line.getViewList().size()==0){  
  11.                 line.addLineView(childView);  
  12.             }else if(line.getWidth()+horizontalSpacing+childView.getMeasuredWidth()>noPaddingWidth) {  
  13.                 //如果当前line的宽+水平间距+childView的宽大于noPaddingWidth,则换行  
  14.                 lineList.add(line);//先保存之前的line对象  
  15.                   
  16.                 line = new Line();//重新创建Line  
  17.                 line.addLineView(childView);//将chidlView放入新的Line  
  18.             }else {  
  19.                 //如果小于noPaddingWidth,则将childView放入当前Line中  
  20.                 line.addLineView(childView);  
  21.             }  
  22.               
  23.             //7.如果当前childView是最后一个,那么就会造成最后的一个Line对象丢失,  
  24.             if(i==(getChildCount()-1)){  
  25.                 lineList.add(line);//保存最后的line对象  
  26.             }  
  27.         }  


设定控件加载的高度

[java]  view plain  copy
  1. //for循环结束后,lineList就存放了所有的Line对象,而每个line中有记录自己的所有TextView  
  2.         //为了能够垂直的摆放所有的Line的TextView,所以要给当前FlowLayout设置对应的宽高,  
  3.         //计算所需要的高度:上下的padding + 所有line的高度   + 所有line之间的垂直间距  
  4.         int height = getPaddingTop()+getPaddingBottom();  
  5.         for (int i = 0; i < lineList.size(); i++) {  
  6.             height += lineList.get(i).getHeight();  
  7.         }  
  8.         height += (lineList.size()-1)*verticalSpacing;  

所以这里使用到了所有行之间的垂直间距,这里可以定义自定属性来设置,也可以设定一个调用方法来进行设置

[html]  view plain  copy
  1. //行与行之间的垂直间距  
  2.     private int verticalSpacing = 10;  
  3.     /**  
  4.      * 设置行与行之间的垂直间距  
  5.      * @param verticalSpacing  
  6.      */  
  7.     public void setVerticalSpacing(int verticalSpacing){  
  8.         if(verticalSpacing>0){  
  9.             this.verticalSpacing = verticalSpacing;  
  10.         }  
  11.     }  

六、 在onLayout方法中进行摆放操作

1.摆放所有的行的时候,我们需要循环取出每一个行Line,其次我们再获取每个Line中的所有的子view,然后再获取每一个子view
2.再摆放每一行中的第一个子view,其次再摆放当前行中的第二个以后的子view,在排放后面的子view的时候,需要参考前面的子view

[java]  view plain  copy
  1. /** 
  2.      * 摆放操作,让所有的子TextView摆放到指定的位置上面 
  3.      */  
  4.     @Override  
  5.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  6.         int paddingLeft = getPaddingLeft();  
  7.         int paddingTop = getPaddingTop();  
  8.         for (int i = 0; i < lineList.size(); i++) {  
  9.             Line line = lineList.get(i);//获取line对象  
  10.               
  11.             //从第二行开始,他们的top总是比上一行多一个行高+垂直间距  
  12.             if(i>0){  
  13.                 paddingTop += lineList.get(i-1).getHeight()+verticalSpacing;  
  14.             }  
  15.             ArrayList<View> viewList = line.getViewList();//获取line所有的TextView  
  16.             //1.计算出当前line的留白区域的值  
  17.             int remainSpacing = getLineRemainSpacing(line);  
  18.             //2.计算每个TextView分到多少留白  
  19.             float perSpacing = remainSpacing/viewList.size();  
  20.               
  21.             for (int j = 0; j < viewList.size(); j++) {  
  22.                 View childView = viewList.get(j);//获取每个TextView  
  23.                 //3.将perSpacing增加到每个TextView的宽度上  
  24.                 int widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (childView.getMeasuredWidth()+perSpacing),MeasureSpec.EXACTLY);  
  25.                 childView.measure(widthMeasureSpec,0);  
  26.                   
  27.                 if(j==0){  
  28.                     //摆放每行的第一个TextView  
  29.                     childView.layout(paddingLeft,paddingTop,paddingLeft+childView.getMeasuredWidth()  
  30.                             ,paddingTop+childView.getMeasuredHeight());  
  31.                 }else {  
  32.                     //摆放后面的TextView,需要参照前一个View  
  33.                     View preView = viewList.get(j-1);  
  34.                     int left = preView.getRight()+horizontalSpacing;  
  35.                     childView.layout(left,preView.getTop(),left+childView.getMeasuredWidth(),   
  36.                             preView.getBottom());  
  37.                 }  
  38.             }  
  39.         }  
  40.     }  

当一行中放不下下一个控件,但是还有很宽的空白处,这时候,我们需要将这段空白计算出来,然后将这段空白平均分配给当前行中的每个子view

[html]  view plain  copy
  1. /**  
  2.      * 获取line的留白区域  
  3.      * @param line  
  4.      * @return  
  5.      */  
  6.     private int getLineRemainSpacing(Line line){  
  7.         return getMeasuredWidth()-getPaddingLeft()-getPaddingRight()-line.getWidth();  
  8.     }  



七、在activity中向布局中添加子View


[html]  view plain  copy
  1. final FlowLayoutView viewById = (FlowLayoutView) findViewById(R.id.framlayout);  
  2.        for (int i = 0; i < mStringArray.length; i++) {  
  3.                final TextView textView = new TextView(this);  
  4.                textView.setText(mStringArray[i]);  
  5.                textView.setTextColor(Color.BLUE);  
  6.                textView.setGravity(Gravity.CENTER);  
  7.                textView.setTextSize(16);  
  8.                textView.setPadding(15, 15, 15, 15);  
  9.                viewById.addView(textView);  
  10.            }  

其中mStringArray是一个保存了String的字符串数组


效果图比较不好看哭


然后我们可以设置下子view的样式背景


[java]  view plain  copy
  1. Drawable normal = generateDrawable(randomColor(), 10);  
  2.            Drawable pressed = generateDrawable(randomColor(), 10);  
  3.            textView.setBackgroundDrawable(generateSelector(pressed, normal));  

这里是我们在java代码中动态创建状态选择器,其中文字的背景是随机生成 的

[java]  view plain  copy
  1. public static StateListDrawable generateSelector(Drawable pressed,Drawable normal){  
  2.        StateListDrawable drawable = new StateListDrawable();  
  3.        drawable.addState(new int[]{android.R.attr.state_pressed}, pressed);//设置按下的图片  
  4.        drawable.addState(new int[]{}, normal);//设置默认的图片  
  5.        return drawable;  
  6.    }  
  7.      
  8.    public  GradientDrawable generateDrawable(int argb,float radius){  
  9.        GradientDrawable drawable = new GradientDrawable();  
  10.        drawable.setShape(GradientDrawable.RECTANGLE);//设置为矩形,默认就是矩形  
  11.        drawable.setCornerRadius(radius);//设置圆角的半径  
  12.        drawable.setColor(argb);  
  13.        return drawable;  
  14.    }  
  15.    /** 
  16.     * 随机生成漂亮的颜色 
  17.     * @return 
  18.     */  
  19.    public  int randomColor(){  
  20.        Random random = new Random();  
  21.        //如果值太大,会偏白,太小则会偏黑,所以需要对颜色的值进行范围限定  
  22.        int red = random.nextInt(150)+50;//50-199  
  23.        int green = random.nextInt(150)+50;//50-199  
  24.        int blue = random.nextInt(150)+50;//50-199  
  25.        return Color.rgb(red, green, blue);//根据rgb混合生成一种新的颜色  
  26.    }  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值