概述
一、首先这是效果
二、实现原理
通过继承ViewGroup,然后在重写 onMeasure测量每个View的宽度,重新onLayout控制每个控件的位置,
并添加点击事件
三、实现
1、在onMeasure方法中得到显示方式,并得到宽高
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int parentTotalWidth = MeasureSpec.getSize(widthMeasureSpec);
//得到真实的宽,去掉左右内边距
int parentWidth = parentTotalWidth - getPaddingLeft() - getPaddingRight();
//得到真实的高,去掉上下内边距
int parentHeight = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
2、遍历子控件,得到他们的宽高,然后和当前控件的宽度去比较,如过小于,当前控件的宽度,则继续向一行添加,如果大于则新添加一行
clearData();
//得到所有的子控件
int childCount = getChildCount();
//遍历
for (int i = 0; i < childCount; i++) {
//得到子控件
View childView = getChildAt(i);
//如果子控件为隐藏,则不计入宽高
if (childView.getVisibility() == View.GONE) {
continue;
}
//创建一个spec文件
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(parentWidth,
widthMode == MeasureSpec.EXACTLY ?
MeasureSpec.AT_MOST : widthMode);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeight,
heightMode == MeasureSpec.EXACTLY ?
MeasureSpec.AT_MOST : heightMode);
//测量view
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mLine == null)
mLine = new CustomLine();
int childWidth = childView.getMeasuredWidth();
//累加使用的宽度
mUsedWidth += childWidth;
//当前行,能显示的下,就在当前显示
if (mUsedWidth <= parentWidth) {
mLine.addView(childView);
mUsedWidth += mHorizontalSpace;//添加上间距
if (mUsedWidth >= parentWidth) {
//如果加上间距大于当前宽度,则换行
if (!isAddLine()) {
break;
}
}
} else {
//显示不了,则需要换行
if (mLine.getLineViewSize() == 0) {
//一行一个都没有,说明标签里的数据太多,但是也得加上,不然就空了一行
mLine.addView(childView);
if (!isAddLine()) {
break;
}
} else {
//该行有数据,但是还是显示 不下,所以直接换行
if (!isAddLine()) {
break;
}
//这是一个新行,所以不管有没有都必须加上去
mLine.addView(childView);
mUsedWidth += childWidth + mHorizontalSpace;
}
}
}
3、在判断当前行是不是为空,因为最后一行如果没放满,则可能不能加入,则需要判断
if (mLine != null && mLine.getLineViewSize() > 0 && !listLines.contains(mLine)) {
//如果最后一行添加了数据,但是没超过当前宽度,则可能不会加入,所以添加判断
listLines.add(mLine);
}
4、得到当前控件的高,就是所有子控件的高之和,最后保存测量宽高
int totalWidth = parentTotalWidth;
int totalHeight = 0;
int lineCount = listLines.size();
for (int i = 0; i < lineCount; i++) {
totalHeight += listLines.get(i).lineMostHeight;
}
totalHeight += mVerticalSpace * (lineCount - 1) + getPaddingBottom() + getPaddingTop();
//保存测量结果
setMeasuredDimension(totalWidth, resolveSize(totalHeight, heightMeasureSpec));
5、重写onLayout进行摆放,判断是不是需要紧凑排序,如果要,则创建一个临时存放所有子View的集合,然后拿到所有的view的宽度,然后用冒泡排序从大到小排列,然后分别用最后一个最大的子view宽度和最小的去匹配,然后在和当前控件的宽去比较,然后在决定是继续添加还是重新创建一行
int left = getPaddingLeft();
int top = getPaddingTop();
try {
if (tempAllView != null && tempAllView.size() != 0) {
tempAllView.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < listLines.size(); i++) {
CustomLine tempLine = listLines.get(i);
for (int j = 0; j < tempLine.lineViews.size(); j++) {
tempAllView.add(tempLine.lineViews.get(j));
}
}
if (isCompact) {
isCompact = !isCompact;
//需要紧凑排列,打乱所有行中的数据,然后重新组装
//把所有view从小到大排列
View tempView = null;
for (int i = 0; i < tempAllView.size() - 1; i++) {
for (int j = 0; j < tempAllView.size() - 1 - i; j++) {
if (tempAllView.get(j).getMeasuredWidth() >
tempAllView.get(j + 1).getMeasuredWidth()) {
tempView = tempAllView.get(j);
tempAllView.set(j, tempAllView.get(j + 1));
tempAllView.set(j + 1, tempView);
}
}
}
clearData();
int viewsCount = tempAllView.size();
int screenWidth = getMeasuredWidth() - getPaddingRight() - getPaddingLeft();
int tempFirstWidth = 0;
if (mLine == null)
mLine = new CustomLine();
//遍历并去匹配
for (int i = 0; i < viewsCount; i++) {
usedList.add(tempAllView.get(viewsCount - 1 - i).hashCode());
tempFirstWidth = tempAllView.get(viewsCount - 1 - i).getMeasuredWidth();
mLine.addView(tempAllView.get(viewsCount - 1 - i));
for (int j = 0; j < viewsCount; j++) {
if (!usedList.contains(tempAllView.get(j).hashCode()) && tempFirstWidth > 0) {
if ((tempFirstWidth + tempAllView.get(j).getMeasuredWidth()) <= screenWidth) {
tempFirstWidth += tempAllView.get(j).getMeasuredWidth();
usedList.add(tempAllView.get(j).hashCode());
mLine.addView(tempAllView.get(j));
} else {
tempFirstWidth = 0;
if (!isAddLine()) {
continue;
}
}
}
}
}
if (mLine != null && mLine.getLineViewSize() > 0 && !listLines.contains(mLine)) {
//如果最后一行添加了数据,但是没超过当前宽度,则可能不会加入,所以添加判断
listLines.add(mLine);
}
}
如果有点击事件,则设置点击事件
if (tagLayoutListener != null) {
for (int i = 0; i < tempAllView.size(); i++) {
tempAllView.get(i).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (tagLayoutListener != null) {
tagLayoutListener.tagLayoutItemClickListener(v);
}
}
});
}
}
最后摆放当前控件
public void LayoutView(int l, int t) {
int left = l;
int top = t;
int count = getLineViewSize();
//得到当前行的总宽度
int totalWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int right = totalWidth;
//得到当前行,使用后剩余的宽度
int surplusWidth = totalWidth - lineTotalWidth - mHorizontalSpace * (count - 1);
if (surplusWidth >= 0) {
//还有剩余
int averageWidth = 0;
try {
averageWidth = (int) (surplusWidth / count + 0.5);
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < count; i++) {
View view = lineViews.get(i);
int childWidth = view.getMeasuredWidth();
int childHeight = view.getMeasuredHeight();
int topOffset = (int) ((lineMostHeight - childHeight) / 2 + 0.5);
if (topOffset < 0) {
topOffset = 0;
}
if (gravity == 2) {
//居中显示,平均剩余宽度到每个view上
childWidth = childWidth + averageWidth;
view.getLayoutParams().width = childWidth;
}
if (averageWidth > 0) {
//子view改变,需重新测量
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
}
// 摆放
if (gravity == 1 || gravity == 2) {
view.layout(left, top + topOffset, left + childWidth, lineMostHeight + topOffset + top);
left += childWidth + mHorizontalSpace;//为下一个view的left赋值
} else if (gravity == 3) {
view.layout(right - childWidth, top + topOffset, right, lineMostHeight + topOffset + top);
right -= childWidth + mHorizontalSpace;//为下一个view的left赋值
}
}
} else {
//没有剩余
if (count == 1) {
//只有一个
View view = lineViews.get(0);
view.layout(left, top, view.getMeasuredWidth(), top + view.getMeasuredHeight());
} else {
System.err.print("this is error");
}
}
}
当然,这里还设置了别的属性,比如从左边开始,或者右边开始,或者居中,是否需要紧凑排列
注意,如果可能出现在子线程刷新UI的情况,可以把requestLayout();放入主线程中
最后是所有源代码
package com.xiaofan.customcontrol;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import org.w3c.dom.Text;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TagLayout extends ViewGroup {
public interface TagLayoutItemClickListener {
void tagLayoutItemClickListener(View view);
}
private TagLayoutItemClickListener tagLayoutListener;
public void setTagLayoutItemClickListener(TagLayoutItemClickListener listener) {
this.tagLayoutListener = listener;
}
public TagLayout(Context context) {
super(context);
}
public TagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//所有的行数
private List<CustomLine> listLines = new ArrayList<>();
private int gravity = 1;
private CustomLine mLine = null;
//最大行数
private int mMaxLines = Integer.MAX_VALUE;
//水平的间距
private int mHorizontalSpace = 0;
//竖直的间距
private int mVerticalSpace = 0;
private boolean isCompact = false;
/**
* @param mgravity 1,left,2,center,3,right
*/
public void setGravity(int mgravity) {
if (this.gravity != mgravity) {
this.gravity = mgravity;
requestLayout();
}
}
/**
* 设置水平的间距
*
* @param space
*/
public void setHorizontalSpace(int space) {
if (this.mHorizontalSpace != space) {
this.mHorizontalSpace = space;
requestLayout();
}
}
/**
* 设置竖直的间距
*
* @param space
*/
public void setVerticalSpace(int space) {
if (this.mVerticalSpace != space) {
this.mVerticalSpace = space;
requestLayout();
}
}
/**
* @param isC 是否要让当前的东西紧凑显示
* 这个很费性能,建议慎用
*/
public void setIsNeedCompact(boolean isC) {
if (this.isCompact != isC) {
this.isCompact = isC;
requestLayout();
}
}
/**
* 设置最大行数
*
* @param maxLines
*/
public void setMaxLines(int maxLines) {
if (this.mMaxLines != maxLines) {
this.mMaxLines = maxLines;
requestLayout();
}
}
//计算已经使用的宽度
private int mUsedWidth = 0;
private List<Integer> usedList = new ArrayList<Integer>();
private List<View> tempAllView = new ArrayList<>();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int parentTotalWidth = MeasureSpec.getSize(widthMeasureSpec);
//得到真实的宽,去掉左右内边距
int parentWidth = parentTotalWidth - getPaddingLeft() - getPaddingRight();
//得到真实的高,去掉上下内边距
int parentHeight = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
clearData();
//得到所有的子控件
int childCount = getChildCount();
//遍历
for (int i = 0; i < childCount; i++) {
//得到子控件
View childView = getChildAt(i);
//如果子控件为隐藏,则不计入宽高
if (childView.getVisibility() == View.GONE) {
continue;
}
//创建一个spec文件
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(parentWidth, widthMode == MeasureSpec.EXACTLY ?
MeasureSpec.AT_MOST : widthMode);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeight, heightMode == MeasureSpec.EXACTLY ?
MeasureSpec.AT_MOST : heightMode);
//测量view
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mLine == null)
mLine = new CustomLine();
int childWidth = childView.getMeasuredWidth();
//累加使用的宽度
mUsedWidth += childWidth;
//当前行,能显示的下,就在当前显示
if (mUsedWidth <= parentWidth) {
mLine.addView(childView);
mUsedWidth += mHorizontalSpace;//添加上间距
if (mUsedWidth >= parentWidth) {
//如果加上间距大于当前宽度,则换行
if (!isAddLine()) {
break;
}
}
} else {
//显示不了,则需要换行
if (mLine.getLineViewSize() == 0) {
//一行一个都没有,说明标签里的数据太多,但是也得加上,不然就空了一行
mLine.addView(childView);
if (!isAddLine()) {
break;
}
} else {
//该行有数据,但是还是显示 不下,所以直接换行
if (!isAddLine()) {
break;
}
//这是一个新行,所以不管有没有都必须加上去
mLine.addView(childView);
mUsedWidth += childWidth + mHorizontalSpace;
}
}
}
if (mLine != null && mLine.getLineViewSize() > 0 && !listLines.contains(mLine)) {
//如果最后一行添加了数据,但是没超过当前宽度,则可能不会加入,所以添加判断
listLines.add(mLine);
}
int totalWidth = parentTotalWidth;
int totalHeight = 0;
int lineCount = listLines.size();
for (int i = 0; i < lineCount; i++) {
totalHeight += listLines.get(i).lineMostHeight;
}
totalHeight += mVerticalSpace * (lineCount - 1) + getPaddingBottom() + getPaddingTop();
//保存测量结果
setMeasuredDimension(totalWidth, resolveSize(totalHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//摆放
if (changed) {
int left = getPaddingLeft();
int top = getPaddingTop();
try {
if (tempAllView != null && tempAllView.size() != 0) {
tempAllView.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < listLines.size(); i++) {
CustomLine tempLine = listLines.get(i);
for (int j = 0; j < tempLine.lineViews.size(); j++) {
tempAllView.add(tempLine.lineViews.get(j));
}
}
if (isCompact) {
isCompact = !isCompact;
//需要紧凑排列,打乱所有行中的数据,然后重新组装
//把所有view从小到大排列
View tempView = null;
for (int i = 0; i < tempAllView.size() - 1; i++) {
for (int j = 0; j < tempAllView.size() - 1 - i; j++) {
if (tempAllView.get(j).getMeasuredWidth() >
tempAllView.get(j + 1).getMeasuredWidth()) {
tempView = tempAllView.get(j);
tempAllView.set(j, tempAllView.get(j + 1));
tempAllView.set(j + 1, tempView);
}
}
}
clearData();
int viewsCount = tempAllView.size();
int screenWidth = getMeasuredWidth() - getPaddingRight() - getPaddingLeft();
int tempFirstWidth = 0;
if (mLine == null)
mLine = new CustomLine();
//遍历并去匹配
for (int i = 0; i < viewsCount; i++) {
usedList.add(tempAllView.get(viewsCount - 1 - i).hashCode());
tempFirstWidth = tempAllView.get(viewsCount - 1 - i).getMeasuredWidth();
mLine.addView(tempAllView.get(viewsCount - 1 - i));
for (int j = 0; j < viewsCount; j++) {
if (!usedList.contains(tempAllView.get(j).hashCode()) && tempFirstWidth > 0) {
if ((tempFirstWidth + tempAllView.get(j).getMeasuredWidth()) <= screenWidth) {
tempFirstWidth += tempAllView.get(j).getMeasuredWidth();
usedList.add(tempAllView.get(j).hashCode());
mLine.addView(tempAllView.get(j));
} else {
tempFirstWidth = 0;
if (!isAddLine()) {
continue;
}
}
}
}
}
if (mLine != null && mLine.getLineViewSize() > 0 && !listLines.contains(mLine)) {
//如果最后一行添加了数据,但是没超过当前宽度,则可能不会加入,所以添加判断
listLines.add(mLine);
}
}
//给每个view设置点击事件
if (tagLayoutListener != null) {
for (int i = 0; i < tempAllView.size(); i++) {
tempAllView.get(i).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (tagLayoutListener != null) {
tagLayoutListener.tagLayoutItemClickListener(v);
}
}
});
}
}
//摆放
for (int i = 0; i < listLines.size(); i++) {
CustomLine line = listLines.get(i);
line.LayoutView(left, top);
top += line.lineMostHeight + mVerticalSpace;
}
}
}
private void clearData() {
try {
listLines.clear();
mLine = new CustomLine();
mUsedWidth = 0;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 是不是新添加一行
*
* @return
*/
private boolean isAddLine() {
if (mLine != null)
listLines.add(mLine);
//如果当前的行数小于最大行数则添加一行
if (listLines.size() < mMaxLines) {
mLine = new CustomLine();
mUsedWidth = 0;
return true;
}
return false;
}
//一行的对象
private class CustomLine {
int lineTotalWidth = 0;//得到该行的所有子View累加宽度
int lineMostHeight = 0;//得到该行子View中最高的
private List<View> lineViews = new ArrayList<>();//存放一行的所有View
public int getLineViewSize() {
return lineViews.size();
}
public void addView(View view) {
lineViews.add(view);
lineTotalWidth += view.getMeasuredWidth();
int childHeight = view.getMeasuredHeight();
lineMostHeight = Math.max(childHeight, lineMostHeight);
}
public void LayoutView(int l, int t) {
int left = l;
int top = t;
int count = getLineViewSize();
//得到当前行的总宽度
int totalWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int right = totalWidth;
//得到当前行,使用后剩余的宽度
int surplusWidth = totalWidth - lineTotalWidth - mHorizontalSpace * (count - 1);
if (surplusWidth >= 0) {
//还有剩余
int averageWidth = 0;
try {
averageWidth = (int) (surplusWidth / count + 0.5);
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < count; i++) {
View view = lineViews.get(i);
int childWidth = view.getMeasuredWidth();
int childHeight = view.getMeasuredHeight();
int topOffset = (int) ((lineMostHeight - childHeight) / 2 + 0.5);
if (topOffset < 0) {
topOffset = 0;
}
if (gravity == 2) {
//居中显示,平均剩余宽度到每个view上
childWidth = childWidth + averageWidth;
view.getLayoutParams().width = childWidth;
}
if (averageWidth > 0) {
//子view改变,需重新测量
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
}
// 摆放
if (gravity == 1 || gravity == 2) {
view.layout(left, top + topOffset, left + childWidth, lineMostHeight + topOffset + top);
left += childWidth + mHorizontalSpace;//为下一个view的left赋值
} else if (gravity == 3) {
view.layout(right - childWidth, top + topOffset, right, lineMostHeight + topOffset + top);
right -= childWidth + mHorizontalSpace;//为下一个view的left赋值
}
}
} else {
//没有剩余
if (count == 1) {
//只有一个
View view = lineViews.get(0);
view.layout(left, top, view.getMeasuredWidth(), top + view.getMeasuredHeight());
} else {
System.err.print("this is error");
}
}
}
}
}
最后
以上就是刻苦流沙为你收集整理的Android 自定义控件之标签控件的全部内容,希望文章能够帮你解决Android 自定义控件之标签控件所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复