Android自定义控件--流式布局(FlowLayout)--自动适配
来源:互联网 发布:eia原油数据 编辑:程序博客网 时间:2024/04/25 21:45
原文链接:
FlowLayout流式布局
在android开发中,随着开发需求的不断提升,android原生的控件在很大程度上已不能满足开发者以及用户的需求,为了更好的增加用户体验,更有利的维护UI,在一个完整的程序中,自定义控件往往是不可或缺的知识,我根据自己的学习经验,现在对自定义控件的分类,以及自定义控件的流程,然后根据FlowLayout案例进行简单分析
一.自定义控件的流程
//1.第一步,测量 onMeasure();//2.第二步,布局 onLayout();//3.第三部,绘制 onDraw()
二.自定义控件的分类
继承View重写onDraw方法
用于实现一些不规则的效果,不方便通过组合的方式达到,需要通过静态或者动态的显示一些不规则的图形的,需要通过绘制的方式实现,这种方法需要手动的填写支持padding和wrap_content方法
继承ViewGroup派生特殊的Layout
这也是我们这此文章介绍的一种自定义布局,即除LinearLayout,RelativeLayout,FrameLayout这几种系统的布局以外的布局,需要稍微的处理元素和子元素的测量和绘制过程,
继承特定的View(比如TextView)
这种方法用来扩展已经有的View的功能,这种方法相对比较简单
继承特定的ViewGroup(比如LinearLayout)
这种方法比较普遍,当某种效果比较像很多种View组合在一起的时候,可以采用这种方法来实现,采用这种方法不需要自己处理ViewGroup的测量和布局这两个过程.上次的文章中介绍了一个PullRefresh(下拉刷新,与加载更多按钮)
下拉刷新,加载很多的地址PullRefresh
三.此次的布局控件时第二种类型,继承ViewGrop派生特殊的Layout
(一).具体作用
FlowLayout是一种流式布局,主要根据自控件的加入顺序进行依次排序,当每一行排满时,进行换行操作,然后根据每一行的未使用空间对View进行屏幕的适配,实例图如下图
(二).原理分析
自定义流式布局的原理大概分为以下几点
(三).具体的代码实现
第一步.首先需要先自定义一个类然后继承ViewGroup
public class FlowLayout extends ViewGroup { public FlowLayout(Context context) { super(context); } public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } //onMeasure()方法,用于View以及自身的测量,是本次自定义控件需要重写的重要的方法之一, @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } //onLayout()方法,主要对View进行布局 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { }}
第二步.我们现在着手看看onMeasure()方法的实现
1.对ViewGrop进行测量,首先要拿到ViewGrop的尺寸,以及测量模式
MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); iMeasureSpec.getMode(widthMeasureSpec); MeasureSpec.getMode(heightMeasureSpec);
2.对所有的子控件进行遍历测量,根据相同方式拿到每个View的尺寸以及测量模式,按照上面的原理分析进行处理,代码如下
1.计算每一行的已经使用的高度
useWidth += childWidth; if(mLine == null){ mLine = new Line(); } if(useWidth < width){
2.如果宽度足够加入下一个View则,将View加入到行中
mLine.addView(childView); useWidth += HorizonytalSpace;
3.第一种情况:当加入View后,再加入间距超出测量范围,必须进行换行操作
if(useWidth >= width){ if(!newLine()){ break; } } }else {
4.第二种情况,剩余控件不足与添加此View,有两种处理方法
//1)但前行中没有其它的元素,此单个View尺寸,超出他的父控件,则必须加入这一行,然后进行换行操作 if(mLine.getLineCount() == 0){ mLine.addView(childView); LineList.add(mLine); if(!newLine()){ break; } }else { //2)但前行已经有其他View剩余控件不够View的放置,新建一行,然后将View加入新的行中 if(!newLine()){ break; } mLine.addView(childView); useWidth += HorizonytalSpace + childWidth; } } }
5.判断最后一行,如果最后一行有子View,并且没有存储,则存储起来
if (mLine != null && mLine.getViewCount() > 0&& !mLines.contains(mLine)) { mLines.add(mLine); }
6.宽度设置好后,对ViewGroup的高度进行测量计算
//高度 = 列间距 + 每一行中最大的MaxHeight的和 for (int i = 0; i <LineList.size() ; i++) { TotalHeight += LineList.get(i).MaxHeight; } TotalHeight += (LineList.size() - 1)*VerticalSpaace + getPaddingTop() + getPaddingBottom();
第三步.对每一行的封装
//在封装的类中,实现两个方法,一个是addView,另一个时对每一行的View进行处理,布局 class Line{ public void addView(View view) public void layoutView(int l,int t){
1.使用循环处理每一个View
for(int i=0;i<mLines.Size();i++)
2.首先对Width来说,计算出剩余的宽度,根据每一行的View的数量进行平均分配,计算得出每一个空间的增加量(widthOffSet)
childWidth += widthOffSet;
3.针对高度,根据MaxHeight,最大高度,计算出每一个View应该增加多少Top直,才能保证View相对于此行直居中.
TopOffSet = TopOffSet>0?TopOffSet:0; view.layout(l,t+ TopOffSet,l +childWidth,t + TopOffSet + childHeight); Left += HorizonytalSpace + childWidth; }
第四步.onLayout()的实现过程
因为在上一步Line的封装中已经对,每一行的View已经进行了布局,所以这里只需要调用即可
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //遍历行集合(LineList), int Top = getPaddingTop(); int Left = getPaddingLeft(); for (int i = 0; i < LineList.size(); i++) { Line line = LineList.get(i); line.layoutView(Left,Top); //每一行的唯一的差别就是首个View的Top不同,动态的改变Top的值 Top += line.MaxHeight + VerticalSpaace; } }
> 到目前为止,整个FlowLayout流式布局打大概知识就全部介绍完毕了,有不足的地方还请大家指正,谢谢了
package com.example.orchid.googleplatstore.ui.View;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import android.widget.ListView;import java.util.ArrayList;/** * Created by orchid * on 16-11-2. */public class MyFlowLayout extends ViewGroup { private int useWidth; private int MaxHeight; private int HorizonytalSpace = 5; private int VerticalSpaace = 5; private Line mLine; private int MaxLine = 100; private ArrayList<Line> LineList = new ArrayList<Line>();// private int public MyFlowLayout(Context context) { super(context); } public MyFlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } public MyFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //获取控件的具体尺寸 int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();//获取空间的宽度 int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();//获取控件的高度 //获取控件的测量模式 int widthMode = MeasureSpec.getMode(widthMeasureSpec);//宽度的测量模式 int heightMode = MeasureSpec.getMode(heightMeasureSpec);//高度的测量模式 //开始遍历所有的子控件 int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); //获取子控件的尺寸,与测量模式 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,widthMode == MeasureSpec.EXACTLY?MeasureSpec.AT_MOST:widthMode); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,heightMode == MeasureSpec.EXACTLY?MeasureSpec.AT_MOST:heightMode); //测量子控件 childView.measure(childWidthMeasureSpec,childHeightMeasureSpec); int childWidth = childView.getMeasuredWidth();//子控件的宽度 int childHeight = childView.getMeasuredHeight();//子控件的高度 useWidth += childWidth; if(mLine == null){ mLine = new Line(); } if(useWidth < width){ //未超过最大限度,可以添加到当前行 mLine.addView(childView); useWidth += HorizonytalSpace; if(useWidth >= width){ if(!newLine()){ break;//创建失败,结束for循环 } } }else { //2.但前行没有控件,必须加入到当前行,然后换行 if(mLine.getLineCount() == 0){ //添加到当前行,然后换行 mLine.addView(childView); LineList.add(mLine); if(!newLine()){ break; } }else { //超过最大高度, //1.当前行有控件,需要新建一行 if(!newLine()){ break; } mLine.addView(childView); useWidth += HorizonytalSpace + childWidth; } } if (mLine != null && mLine.getLineCount() > 0 && !LineList.contains(mLine)) { // 由于前面采用判断长度是否超过最大宽度来决定是否换行,则最后一行可能因为还没达到最大宽度,所以需要验证后加入集合中 LineList.add(mLine); } } //为控件设置宽度,高度 int Totalwidth = MeasureSpec.getSize(widthMeasureSpec); int TotalHeight = 0; for (int i = 0; i <LineList.size() ; i++) { TotalHeight += LineList.get(i).MaxHeight; } TotalHeight += (LineList.size() - 1)*VerticalSpaace + getPaddingTop() + getPaddingBottom(); setMeasuredDimension(Totalwidth,TotalHeight);// super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //遍历行集合(LineList), int Top = getPaddingTop(); int Left = getPaddingLeft(); for (int i = 0; i < LineList.size(); i++) { Line line = LineList.get(i); line.layoutView(Left,Top); Top += line.MaxHeight + VerticalSpaace; } } private boolean newLine(){ //判断是否超过最大行数 if(LineList.size() < MaxLine){ //将上一行添加到,LineList中 LineList.add(mLine); mLine = new Line();//创建一个新的行 //新的一行,使用的数据为0 useWidth = 0; MaxHeight = 0; return true;//创建成功返回true } return false; } //创建一个类,用来处理每一行的数据 class Line{ private int mLineWidth = 0; private int MaxHeight = 0; private ArrayList<View> viewlist = new ArrayList<View>(); public void addView(View view){ viewlist.add(view); mLineWidth += view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); MaxHeight = MaxHeight < childHeight?childHeight:MaxHeight; } public int getLineCount(){ return viewlist.size(); } public void layoutView(int l,int t){ //对此行的数据进行布局 int Left = l; int Top = t; int childCount = viewlist.size(); int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -(childCount-1) * HorizonytalSpace; //计算剩余宽度 int surplusWidth = width - mLineWidth; if(surplusWidth > 0){ //计算每个布局的添加量 int widthOffSet = (int) (surplusWidth * 1.0f/viewlist.size() + 0.5f); for (int i = 0; i < viewlist.size(); i++) { View view = viewlist.get(i); int childWidth = view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); childWidth += widthOffSet;//重新分配控件的高度 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth,MeasureSpec.EXACTLY); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight,MeasureSpec.EXACTLY); view.measure(childWidthMeasureSpec,childHeightMeasureSpec); //分配布局控件时的偏移量 int TopOffSet = (MaxHeight - childHeight) / 2; TopOffSet = TopOffSet>0?TopOffSet:0;//如果TopOffSet(竖直方向的偏移量)小于0,则设置为0; view.layout(Left,Top+ TopOffSet,Left +childWidth,Top + TopOffSet + childHeight); Left += HorizonytalSpace + childWidth; } }else{ } } } public void setHorizontalSpacing(int horizonytalSpace) { HorizonytalSpace = horizonytalSpace; } public void setVerticalSpacing(int verticalSpaace) { VerticalSpaace = verticalSpaace; }}
1 0
- Android自定义控件--流式布局(FlowLayout)--自动适配
- Android自定义控件--流式布局(FlowLayout)
- 自定义流式布局控件FlowLayout
- 自定义控件之-流式布局FlowLayout
- Android自定义流式布局-FlowLayout
- Android自定义流式布局-FlowLayout
- 自定义流式布局FlowLayout
- 自定义流式布局FlowLayout
- 自定义控件之流式布局FlowLayout
- Android 自定义FlowLayout布局
- Android 流式布局FlowLayout
- Android流式布局-FlowLayout
- Android流式布局FlowLayout
- Android:FlowLayout流式布局
- Android FlowLayout 流式布局
- Android 自动换行布局 FlowLayout
- 自定义ViewGroup实现流式布局FlowLayout
- 自定义ViewGroup,流式布局FlowLayout
- 由通项为In(1+1\n)的级数引申...
- poj 1830 高斯消元初步
- java.util.concurrent.atomic.AtomicInteger(Atomic详解)
- 记录那些年ionic~~~2016666666
- (zTree)关于叶子节点新增子节点,树无法异步刷新问题
- Android自定义控件--流式布局(FlowLayout)--自动适配
- 大数据开发利器:Hadoop(6)-HBase第三讲 Java 开发基础
- Intellij IDEA通过tomcat部署web项目的机制
- Pymode requires vim compiled with +python. Most of features will be disabled.
- matlab 按某一列排序
- macos缺少freetype终极解决方案
- 欢迎使用CSDN-markdown编辑器
- 深度学习笔记:稀疏自编码器(4)——稀疏自编码器代码练习
- android BroadcastReceiver全面理解,轻松掌握