原文链接:
在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
FlowLayout是一种流式布局,主要根据自控件的加入顺序进行依次排序,当每一行排满时,进行换行操作,然后根据每一行的未使用空间对View进行屏幕的适配,实例图如下图
自定义流式布局的原理大概分为以下几点
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) {
}
}
1.对ViewGrop进行测量,首先要拿到ViewGrop的尺寸,以及测量模式
MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
iMeasureSpec.getMode(widthMeasureSpec);
MeasureSpec.getMode(heightMeasureSpec);
2.对所有的子控件进行遍历测量,根据相同方式拿到每个View的尺寸以及测量模式,按照上面的原理分析进行处理,代码如下
useWidth += childWidth;
if(mLine == null){
mLine = new Line();
}
if(useWidth < width){
mLine.addView(childView);
useWidth += HorizonytalSpace;
if(useWidth >= width){
if(!newLine()){
break;
}
}
}else {
//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;
}
}
}
if (mLine != null && mLine.getViewCount() > 0&& !mLines.contains(mLine)) {
mLines.add(mLine);
}
//高度 = 列间距 + 每一行中最大的MaxHeight的和
for (int i = 0; i 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){
for(int i=0;i<mLines.Size();i++)
childWidth += widthOffSet;
TopOffSet = TopOffSet>0?TopOffSet:0;
view.layout(l,t+ TopOffSet,l +childWidth,t + TopOffSet + childHeight);
Left += HorizonytalSpace + childWidth;
}
因为在上一步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 LineList = new ArrayList();
// 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 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 viewlist = new ArrayList();
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;
}
}