效果图:
一、首先创建我 们的自定义流式布局
public class FlowLayoutVIEw extends VIEwGroup { public FlowLayoutVIEw(Context context) { this(context, null); } public FlowLayoutVIEw(Context context, AttributeSet attrs) { this(context, attrs,0); } public FlowLayoutVIEw(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @OverrIDe protected voID onMeasure(int wIDthMeasureSpec, int heightmeasureSpec) { super.onMeasure(wIDthMeasureSpec, heightmeasureSpec); } @OverrIDe protected voID onLayout(boolean changed, int l, int t, int r, int b) { }}
二、在布局文件中使用我们的控件
<com.androIDlongs.flowlayoutvIEwapplication.FlowLayoutVIEw androID:ID="@+ID/flowlayout" androID:layout_wIDth="match_parent" androID:layout_height="match_parent"></com.androIDlongs.flowlayoutvIEwapplication.FlowLayoutVIEw>
三、在 activity中向我们的控件中添加子vIEw四、定义一行对象,来保存每行中所包含的控件信息
class line{ //用来记录当前行的所有TextVIEw private ArrayList<VIEw> vIEwList = new ArrayList<VIEw>(); //表示当前行所有TextVIEw的宽,还有他们之间的水平间距 private int wIDth; //当前行的高度 private int height; }}
五、 在onMeasure方法中完成测量分行 *** 作
@OverrIDe protected voID onMeasure(int wIDthMeasureSpec, int heightmeasureSpec) { super.onMeasure(wIDthMeasureSpec, heightmeasureSpec); //1.获取FlowLayout的宽度 int wIDth = MeasureSpec.getSize(wIDthMeasureSpec); //2.计算用于实际比较的宽度,就是wIDth减去左右的padding值 int nopaddingWIDth = wIDth - getpaddingleft()- getpaddingRight(); //3.遍历所有的子TextVIEw,在遍历过程中进行比较,进行分行 *** 作 //创建一个行 line line = new line(); for (int i = 0; i < getChildCount(); i++) { final VIEw childAt = getChildAt(i); //1. 当 当前的行中没有子VIEw,那么直接将子childAt直接放入行中,而不用再比较宽度,要保证 if(line.getVIEwList().size()==0){ line.addlineVIEw(childAt); } // 每行至少有一个子TextVIEw } }
可以看到这里有一个addlineVIEw方法,我们需要在line中定义addlineVIEw这个方法 line对象的addlineVIEw方法简析
1.当我们将vIEw添加到line去,我们就需要将子VIEw添加到保存子VIEw的集合(vIEwList)中去,并且要更新line的wIDth,
2.那么在更新wIDth的过程中,如果当前添加的子VIEw是当前行中的第一个子控件,那么当前子VIEw的宽就是当前行的宽
如果不是第一个,则要在当前wIDth的基础上+水平间距+当前添加子VIEw的宽度
3.在这里使用到和水平间距,所以需要定义当前行中的每个子VIEw的水平间距,
private final int DEFAulT_SPACING = 10; private int horizontalSpacing = DEFAulT_SPACING;//水平间距
4. 通过自定义属性的方式来实现设定水平间距
1. 在values文件夹下新建attrs.xml文件
<?xml version="1.0" enCoding="utf-8"?> <resources> <attr name="horizontalSpacing" format="integer"/> <declare-styleable name="FlowLayoutVIEw"> <attr name="horizontalSpacing"/> </declare-styleable> </resources>
2. 在自定义FlowLayoutVIEw的构造方法中获取我们所设定的水平间距public FlowLayoutVIEw(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //获取所有和自定义属性和样式 final TypedArray typedArray = context.gettheme().obtainStyledAttributes(attrs, R.styleable.FlowLayoutVIEw, defStyleAttr, 0); final int indexCount = typedArray.getIndexCount(); for (int i = 0; i < indexCount; i++) { final int indexAttr = typedArray.getIndex(i); switch (indexAttr) { case R.styleable.FlowLayoutVIEw_horizontalSpacing: horizontalSpacing = typedArray.getInt(indexAttr,10); break; } } typedArray.recycle(); }
5. 通过向外暴露一个方法来设置
/** * 设置子VIEw直接的水平间距 * @param horizontalSpacing*/public voID setHorizontalSpacing(int horizontalSpacing){ if(horizontalSpacing>0){ this.horizontalSpacing = horizontalSpacing; } }
分行逻辑简析6. 添加子vIEw后,更新当前行的高度,实际上在这里,每个子VIEw的高度就是当前行的高度
public voID addlineVIEw(VIEw lineVIEw){ if(!vIEwList.contains(lineVIEw)){ vIEwList.add(lineVIEw); //更新wIDth if(vIEwList.size()==1){ //如果是第一个TextVIEw,那么wIDth就是lineVIEw的宽度 wIDth = lineVIEw.getMeasureDWIDth(); }else { //如果不是第一个,则要在当前wIDth的基础上+水平间距+lineVIEw的宽度 wIDth += horizontalSpacing + lineVIEw.getMeasureDWIDth(); } //更新height,在此所有的TextVIEw的高度都是一样的 height = Math.max(height,lineVIEw.getMeasuredHeight()); } }
当当前行中已经有了子vIEw后,当前行的宽度+水平间距+当前添加的子VIEw的宽度大于控件的宽度的时候,需要进行换行 *** 作,那么在换行 *** 作之前,需要先保存之前的行,所以这里使用集合来进行保存
private ArrayList<line> lineList = new ArrayList<FlowLayout.line>();
当前行的宽度+水平间距+当前添加的子VIEw的宽度不大于控件的宽度的时候,直接将当前子VIEw添加到当前行中去
//遍历所有的子TextVIEw,进行分行 *** 作 line line = new line();//只要不换行,始终都是同一个line对象 for (int i = 0; i<getChildCount(); i++) { //获取子TextVIEw VIEw childVIEw = getChildAt(i); //引起vIEw的onMeasure方法回调,从而保证后面的方法能够有值 childVIEw.measure(0,0); //如果当前line中 没有TextVIEw,则直接放入当前line中 if(line.getVIEwList().size()==0){ line.addlineVIEw(childVIEw); }else if(line.getWIDth()+horizontalSpacing+childVIEw.getMeasureDWIDth()>nopaddingWIDth) { //如果当前line的宽+水平间距+childVIEw的宽大于nopaddingWIDth,则换行 lineList.add(line);//先保存之前的line对象 line = new line();//重新创建line line.addlineVIEw(childVIEw);//将chIDlVIEw放入新的line }else { //如果小于nopaddingWIDth,则将childVIEw放入当前line中 line.addlineVIEw(childVIEw); } //7.如果当前childVIEw是最后一个,那么就会造成最后的一个line对象丢失, if(i==(getChildCount()-1)){ lineList.add(line);//保存最后的line对象 } }
设定控件加载的高度
//for循环结束后,lineList就存放了所有的line对象,而每个line中有记录自己的所有TextVIEw //为了能够垂直的摆放所有的line的TextVIEw,所以要给当前FlowLayout设置对应的宽高, //计算所需要的高度:上下的padding + 所有line的高度 + 所有line之间的垂直间距 int height = getpaddingtop()+getpaddingBottom(); for (int i = 0; i < lineList.size(); i++) { height += lineList.get(i).getHeight(); } height += (lineList.size()-1)*verticalSpacing;
所以这里使用到了所有行之间的垂直间距,这里可以定义自定属性来设置,也可以设定一个调用方法来进行设置
//行与行之间的垂直间距 private int verticalSpacing = 10; /** * 设置行与行之间的垂直间距 * @param verticalSpacing */ public voID setVerticalSpacing(int verticalSpacing){ if(verticalSpacing>0){ this.verticalSpacing = verticalSpacing; } }
六、 在onLayout方法中进行摆放 *** 作
1.摆放所有的行的时候,我们需要循环取出每一个行line,其次我们再获取每个line中的所有的子vIEw,然后再获取每一个子vIEw
2.再摆放每一行中的第一个子vIEw,其次再摆放当前行中的第二个以后的子vIEw,在排放后面的子vIEw的时候,需要参考前面的子vIEw
/** * 摆放 *** 作,让所有的子TextVIEw摆放到指定的位置上面 */ @OverrIDe protected voID onLayout(boolean changed, int l, int t, int r, int b) { int paddingleft = getpaddingleft(); int paddingtop = getpaddingtop(); for (int i = 0; i < lineList.size(); i++) { line line = lineList.get(i);//获取line对象 //从第二行开始,他们的top总是比上一行多一个行高+垂直间距 if(i>0){ paddingtop += lineList.get(i-1).getHeight()+verticalSpacing; } ArrayList<VIEw> vIEwList = line.getVIEwList();//获取line所有的TextVIEw //1.计算出当前line的留白区域的值 int remainSpacing = getlineRemainSpacing(line); //2.计算每个TextVIEw分到多少留白 float perSpacing = remainSpacing/vIEwList.size(); for (int j = 0; j < vIEwList.size(); j++) { VIEw childVIEw = vIEwList.get(j);//获取每个TextVIEw //3.将perSpacing增加到每个TextVIEw的宽度上 int wIDthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (childVIEw.getMeasureDWIDth()+perSpacing),MeasureSpec.EXACTLY); childVIEw.measure(wIDthMeasureSpec,0); if(j==0){ //摆放每行的第一个TextVIEw childVIEw.layout(paddingleft,paddingtop,paddingleft+childVIEw.getMeasureDWIDth() ,paddingtop+childVIEw.getMeasuredHeight()); }else { //摆放后面的TextVIEw,需要参照前一个VIEw VIEw preVIEw = vIEwList.get(j-1); int left = preVIEw.getRight()+horizontalSpacing; childVIEw.layout(left,preVIEw.gettop(),left+childVIEw.getMeasureDWIDth(), preVIEw.getBottom()); } } } }
当一行中放不下下一个控件,但是还有很宽的空白处,这时候,我们需要将这段空白计算出来,然后将这段空白平均分配给当前行中的每个子vIEw
/** * 获取line的留白区域 * @param line * @return */ private int getlineRemainSpacing(line line){ return getMeasureDWIDth()-getpaddingleft()-getpaddingRight()-line.getWIDth(); }
七、在activity中向布局中添加子VIEw
final FlowLayoutVIEw vIEwByID = (FlowLayoutVIEw) findVIEwByID(R.ID.framlayout); for (int i = 0; i < mStringArray.length; i++) { final TextVIEw textVIEw = new TextVIEw(this); textVIEw.setText(mStringArray[i]); textVIEw.setTextcolor(color.BLUE); textVIEw.setGravity(Gravity.CENTER); textVIEw.setTextSize(16); textVIEw.setpadding(15, 15, 15, 15); vIEwByID.addVIEw(textVIEw); }
其中mStringArray是一个保存了String的字符串数组
效果图比较不好看
然后我们可以设置下子vIEw的样式背景
Drawable normal = generateDrawable(randomcolor(), 10); Drawable pressed = generateDrawable(randomcolor(), 10); textVIEw.setBackgroundDrawable(generateSelector(pressed, normal));
这里是我们在java代码中动态创建状态选择器,其中文字的背景是随机生成 的
public static StateListDrawable generateSelector(Drawable pressed,Drawable normal){ StateListDrawable drawable = new StateListDrawable(); drawable.addState(new int[]{androID.R.attr.state_pressed}, pressed);//设置按下的图片 drawable.addState(new int[]{}, normal);//设置默认的图片 return drawable; } public GradIEntDrawable generateDrawable(int argb,float radius){ GradIEntDrawable drawable = new GradIEntDrawable(); drawable.setShape(GradIEntDrawable.RECTANGLE);//设置为矩形,默认就是矩形 drawable.setCornerRadius(radius);//设置圆角的半径 drawable.setcolor(argb); return drawable; } /** * 随机生成漂亮的颜色 * @return */ public int randomcolor(){ Random random = new Random(); //如果值太大,会偏白,太小则会偏黑,所以需要对颜色的值进行范围限定 int red = random.nextInt(150)+50;//50-199 int green = random.nextInt(150)+50;//50-199 int blue = random.nextInt(150)+50;//50-199 return color.rgb(red, green, blue);//根据rgb混合生成一种新的颜色 }
点击下载本节源码
访问密码:4vrl
AndroID自定义控件ImageViwe(一)——依据控件的大小来设置缩放图片显示
点击打开链接
AndroID自定义ImageVIEw(二)——实现双击放大与缩小图片
点击打开链接
AndroID自定义控件ImageViwe(三)——随手指进行图片的缩放
点击打开链接
AndroID自定义控件ImageViwe(四)——多点触控实现图片的自由移动
点击打开链接
AndroID ListVIEw分组排序显示数据
点击打开链接
AndroID自定义下拉刷新功能的ListVIEw
点击打开链接
AndroID音乐播放器高级开发
点击打开链接
本章智力解答: 在一个房间里,有油灯 ,暖炉及壁炉。现在,想要用一根火柴将三个器具点燃,请问首先应该点燃哪一个?
都不是 ,应当先点火柴,只有点燃了火柴才能去点其他东西
以上是内存溢出为你收集整理的Android自定义控件之流式布局全部内容,希望文章能够帮你解决Android自定义控件之流式布局所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)