Android自定义控件--流式布局(FlowLayout)--自动适配

来源:互联网 发布:eia原油数据 编辑:程序博客网 时间:2024/04/25 21:45

原文链接:

FlowLayout流式布局


在android开发中,随着开发需求的不断提升,android原生的控件在很大程度上已不能满足开发者以及用户的需求,为了更好的增加用户体验,更有利的维护UI,在一个完整的程序中,自定义控件往往是不可或缺的知识,我根据自己的学习经验,现在对自定义控件的分类,以及自定义控件的流程,然后根据FlowLayout案例进行简单分析

一.自定义控件的流程

//1.第一步,测量    onMeasure();//2.第二步,布局    onLayout();//3.第三部,绘制    onDraw()

二.自定义控件的分类

  1. 继承View重写onDraw方法 

        用于实现一些不规则的效果,不方便通过组合的方式达到,需要通过静态或者动态的显示一些不规则的图形的,需要通过绘制的方式实现,这种方法需要手动的填写支持padding和wrap_content方法  
  2. 继承ViewGroup派生特殊的Layout

    这也是我们这此文章介绍的一种自定义布局,即除LinearLayout,RelativeLayout,FrameLayout这几种系统的布局以外的布局,需要稍微的处理元素和子元素的测量和绘制过程,
  3. 继承特定的View(比如TextView)

      这种方法用来扩展已经有的View的功能,这种方法相对比较简单
  4. 继承特定的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
原创粉丝点击