我是靠谱客的博主 淡淡小虾米,最近开发中收集的这篇文章主要介绍Android流式标签布局,自定义标签控件tagView,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

                               我们在一些项目中会用到自定义流式布局,我个人觉得流式布局将呆板的布局错综排列,来提升用户体验度.(还可以不辜负美工妹子们的期望偷笑,人家毕竟也辛辛苦苦设计半天)。今天终于有时间来做做了。写的不好,很多地方值得改进望大家一起交流。

                  这是效果图:

                                         

                  

               实现基本功能:

                                首先来说明几点:

                                1.标签视图TagView直接继承TextView,这样有几个好处:不用去重写onMeasure()接口,                                              不用自己绘制Text,对Text控制也方便;
                                 2.标签布局TagGroup继承ViewGroup,需要重写onMeasure()和onLayout()方法来控制                                                     TagView的显示;

                   

                1. 实现TagView:

                                         

public class TagView extends TextView {
// 3种模式:圆角矩形、圆弧、直角矩形
public final static int MODE_ROUND_RECT = 1;
public final static int MODE_ARC = 2;
public final static int MODE_RECT = 3;
private Paint mPaint;
// 背景色
private int mBgColor;
// 边框颜色
private int mBorderColor;
// 边框大小
private float mBorderWidth;
// 边框角半径
private float mRadius;
// Tag内容
private CharSequence mTagText;
// 字体水平空隙
private int mHorizontalPadding;
// 字体垂直空隙
private int mVerticalPadding;
// 边框矩形
private RectF mRect;
// 调整标志位,只做一次
private boolean mIsAdjusted = false;
// 点击监听器
private OnTagClickListener mTagClickListener;
// 显示模式
private int mTagMode = MODE_ROUND_RECT;
public TagView(Context context, String text) {
super(context);
setText(text);
_init(context);
}
public TagView(Context context, AttributeSet attrs) {
super(context, attrs);
_init(context);
}
/**
* 初始化
*
* @param context
*/
private void _init(Context context) {
mRect = new RectF();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTagText = getText();
// 设置字体占中
setGravity(Gravity.CENTER);
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mTagClickListener != null) {
mTagClickListener.onTagClick(String.valueOf(mTagText));
}
}
});
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mTagClickListener != null) {
mTagClickListener.onTagLongClick(String.valueOf(mTagText));
}
return true;
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
_adjustText();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 设置矩形边框
mRect.set(mBorderWidth, mBorderWidth, w - mBorderWidth, h
- mBorderWidth);
}
@Override
protected void onDraw(Canvas canvas) {
// 绘制背景
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mBgColor);
float radius = mRadius;
if (mTagMode == MODE_ARC) {
radius = mRect.height() / 2;
} else if (mTagMode == MODE_RECT) {
radius = 0;
}
canvas.drawRoundRect(mRect, radius, radius, mPaint);
// 绘制边框
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mBorderWidth);
mPaint.setColor(mBorderColor);
canvas.drawRoundRect(mRect, radius, radius, mPaint);
super.onDraw(canvas);
}
/**
* 调整内容,如果超出可显示的范围则做裁剪
*/
private void _adjustText() {
if (mIsAdjusted) {
return;
}
mIsAdjusted = true;
// 获取可用宽度
int availableWidth = ((TagGroup) getParent()).getAvailableWidth();
mPaint.setTextSize(getTextSize());
// 计算字符串长度
float textWidth = mPaint.measureText(String.valueOf(mTagText));
// 如果可用宽度不够用,则做裁剪处理,末尾不3个.
if (textWidth + mHorizontalPadding * 2 > availableWidth) {
float pointWidth = mPaint.measureText(".");
// 计算能显示的字体长度
float maxTextWidth = availableWidth - mHorizontalPadding * 2
- pointWidth * 3;
float tmpWidth = 0;
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < mTagText.length(); i++) {
char c = mTagText.charAt(i);
float cWidth = mPaint.measureText(String.valueOf(c));
// 计算每个字符的宽度之和,如果超过能显示的长度则退出
if (tmpWidth + cWidth > maxTextWidth) {
break;
}
strBuilder.append(c);
tmpWidth += cWidth;
}
// 末尾添加3个.并设置为显示字符
strBuilder.append("...");
setText(strBuilder.toString());
}
}
/******************************************************************/
public int getBgColor() {
return mBgColor;
}
public void setBgColor(int bgColor) {
mBgColor = bgColor;
}
public int getBorderColor() {
return mBorderColor;
}
public void setBorderColor(int borderColor) {
mBorderColor = borderColor;
}
public float getBorderWidth() {
return mBorderWidth;
}
public void setBorderWidth(float borderWidth) {
mBorderWidth = borderWidth;
}
public float getRadius() {
return mRadius;
}
public void setRadius(float radius) {
mRadius = radius;
}
public int getHorizontalPadding() {
return mHorizontalPadding;
}
public void setHorizontalPadding(int horizontalPadding) {
mHorizontalPadding = horizontalPadding;
setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding,
mVerticalPadding);
}
public int getVerticalPadding() {
return mVerticalPadding;
}
public void setVerticalPadding(int verticalPadding) {
mVerticalPadding = verticalPadding;
setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding,
mVerticalPadding);
}
public CharSequence getTagText() {
return mTagText;
}
public void setTagText(CharSequence tagText) {
mTagText = tagText;
}
/********************************* 点击监听 *********************************/
public OnTagClickListener getTagClickListener() {
return mTagClickListener;
}
public void setTagClickListener(OnTagClickListener tagClickListener) {
mTagClickListener = tagClickListener;
}
/**
* 点击监听器
*/
public interface OnTagClickListener {
void onTagClick(String text);
void onTagLongClick(String text);
}
/********************************* 显示模式 *********************************/
public int getTagMode() {
return mTagMode;
}
public void setTagMode(@TagMode int tagMode) {
mTagMode = tagMode;
}
@IntDef({ MODE_ROUND_RECT, MODE_ARC, MODE_RECT })
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
public @interface TagMode {
}
}
                      其实还是很简单的,主要通过一些属性来设置绘制的效果,包括背景、边框和文字。在代码中设置了文字占中,并在onSizeChanged()方法中设置了边框矩形,其它就没什么了看代码就好了。


                 2.ViewGroup的实现:

public class TagGroup extends ViewGroup {
private Paint mPaint;
// 背景色
private int mBgColor;
// 边框颜色
private int mBorderColor;
// 边框大小
private float mBorderWidth;
// 边框角半径
private float mRadius;
// Tag之间的垂直间隙
private int mVerticalInterval;
// Tag之间的水平间隙
private int mHorizontalInterval;
// 边框矩形
private RectF mRect;
public TagGroup(Context context) {
this(context, null);
}
public TagGroup(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public TagGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
_init(context);
}
private void _init(Context context) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBgColor = Color.parseColor("#11FF0000");
mBorderColor = Color.parseColor("#22FF0000");
mBorderWidth = MeasureUtils.dp2px(context, 1f);
mRadius = MeasureUtils.dp2px(context, 5f);
int defaultInterval = (int) MeasureUtils.dp2px(context, 5f);
mHorizontalInterval = defaultInterval;
mVerticalInterval = defaultInterval;
mRect = new RectF();
// 如果想要自己绘制内容,则必须设置这个标志位为false,否则onDraw()方法不会调用
setWillNotDraw(false);
setPadding(defaultInterval, defaultInterval, defaultInterval, defaultInterval);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
// 计算可用宽度,为测量宽度减去左右padding值
int availableWidth = widthSpecSize - getPaddingLeft() - getPaddingRight();
// 测量子视图
measureChildren(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
int tmpWidth = 0;
int measureHeight = 0;
int maxLineHeight = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 记录该行的最大高度
if (maxLineHeight == 0) {
maxLineHeight = child.getMeasuredHeight();
} else {
maxLineHeight = Math.max(maxLineHeight, child.getMeasuredHeight());
}
// 统计该行TagView的总宽度
tmpWidth += child.getMeasuredWidth() + mHorizontalInterval;
// 如果超过可用宽度则换行
if (tmpWidth - mHorizontalInterval > availableWidth) {
// 统计TagGroup的测量高度,要加上垂直间隙
measureHeight += maxLineHeight + mVerticalInterval;
// 重新赋值
tmpWidth = child.getMeasuredWidth() + mHorizontalInterval;
maxLineHeight = child.getMeasuredHeight();
}
}
// 统计TagGroup的测量高度,加上最后一行
measureHeight += maxLineHeight;
// 设置测量宽高,记得算上padding
if (childCount == 0) {
setMeasuredDimension(0, 0);
} else if (heightSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize, measureHeight + getPaddingTop() + getPaddingBottom());
} else {
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
if (childCount <= 0) {
return;
}
int availableWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
// 当前布局使用的top坐标
int curTop = getPaddingTop();
// 当前布局使用的left坐标
int curLeft = getPaddingLeft();
int maxHeight = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (maxHeight == 0) {
maxHeight = child.getMeasuredHeight();
} else {
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
// 超过一行做换行操作
if (width + curLeft > availableWidth) {
curLeft = getPaddingLeft();
// 计算top坐标,要加上垂直间隙
curTop += maxHeight + mVerticalInterval;
maxHeight = child.getMeasuredHeight();
}
// 设置子视图布局
child.layout(curLeft, curTop, curLeft + width, curTop + height);
// 计算left坐标,要加上水平间隙
curLeft += width + mHorizontalInterval;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRect.set(mBorderWidth, mBorderWidth, w - mBorderWidth, h - mBorderWidth);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制背景
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mBgColor);
canvas.drawRoundRect(mRect, mRadius, mRadius, mPaint);
// 绘制边框
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mBorderWidth);
mPaint.setColor(mBorderColor);
canvas.drawRoundRect(mRect, mRadius, mRadius, mPaint);
}
/******************************************************************/
/**
* 添加Tag
* @param text tag内容
*/
public void addTag(String text) {
addView(new TagView(getContext(), text));
}
public void addTags(String... textList) {
for (String text : textList) {
addTag(text);
}
}
public void cleanTags() {
removeAllViews();
postInvalidate();
}
public void setTags(String... textList) {
cleanTags();
addTags(textList);
}
}
                其实代码主要看onMeasure()和onLayout()两个方法。 在onMeasure()我们要对布局进行测量,遍历所有子视图来计算布局的最终宽高,需要注意的是要把布局的padding属性计算上去,所以布局可用宽度为测量宽度减去左右两边的padding值,除了padding需要计算外,还要计算上TagView之间的间隙值。具体的测量过程代码注释的挺清楚,看下就懂了。

                 然后再看onLayout(),这个和onMeasure()其实挺像的,同样要计算上padding和间隙值,然后就是一个一个算出每个TagView的上下左右坐标,再调用TagView的layout()方法来设置到布局中的相应位置。


                 在写测试的时候我遇到一个问题:字符串过长的问题,因此需要裁剪。我的思路是这样:

                  首先太长的字符串截取前面的部分,并在后面补上3个“.”,就类似省略号;既然要裁剪就要知道最大可用的布局宽度,这个要从父布局中获取,需要TagGroup提供接口;最后计算的时候也要算上TagView的padding值,然后一个字符一个字符测量到符合要求;

                  

/**
* 调整内容,如果超出可显示的范围则做裁剪
*/
private void _adjustText() {
if (mIsAdjusted) {
return;
}
mIsAdjusted = true;
// 获取可用宽度
int availableWidth = ((TagGroup) getParent()).getAvailableWidth();
mPaint.setTextSize(getTextSize());
// 计算字符串长度
float textWidth = mPaint.measureText(String.valueOf(mTagText));
// 如果可用宽度不够用,则做裁剪处理,末尾不3个.
if (textWidth + mHorizontalPadding * 2 > availableWidth) {
float pointWidth = mPaint.measureText(".");
// 计算能显示的字体长度
float maxTextWidth = availableWidth - mHorizontalPadding * 2 - pointWidth * 3;
float tmpWidth = 0;
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < mTagText.length(); i++) {
char c = mTagText.charAt(i);
float cWidth = mPaint.measureText(String.valueOf(c));
// 计算每个字符的宽度之和,如果超过能显示的长度则退出
if (tmpWidth + cWidth > maxTextWidth) {
break;
}
strBuilder.append(c);
tmpWidth += cWidth;
}
// 末尾添加3个.并设置为显示字符
strBuilder.append("...");
setText(strBuilder.toString());
}

                  3.这是MainActivity:

public class MainActivity extends Activity {
private String[] mTagWords = new String[] {
"Hello",
"Android",
"我是TagView",
"This is a long string, This is a long string, This is a long string",
"这是长字符串,这是长字符串,这是长字符串,这是长字符串", "故事开始在最初的那个梦中", "赛任的歌会让人忘记初衷",
"我会想奥德修斯一样" };
private TagGroup mTagGroup;
private Button mBtnAdd;
private Button mBtnClean;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTagGroup = (TagGroup) findViewById(R.id.tag_group);
mBtnAdd = (Button) findViewById(R.id.btn_add);
mBtnClean = (Button) findViewById(R.id.btn_clean);
mBtnAdd.setOnClickListener(new View.OnClickListener() {
Random random = new Random();
@Override
public void onClick(View arg0) {
mTagGroup.addTag(mTagWords[random.nextInt(mTagWords.length)]);
}
});
mBtnClean.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
mTagGroup.cleanTags();
}
});
mTagGroup.setTags(mTagWords);
mTagGroup.setTagBgColor(getResources().getColor(
android.R.color.holo_red_light));
mTagGroup.setTagBorderColor(getResources().getColor(
android.R.color.holo_red_dark));
mTagGroup.setTagTextColor(Color.WHITE);
mTagGroup.setTagMode(TagView.MODE_ARC);
mTagGroup.setBgColor(getResources().getColor(
android.R.color.holo_orange_light));
mTagGroup.setBorderColor(getResources().getColor(
android.R.color.holo_blue_dark));
mTagGroup.setBorderWidth(1);
mTagGroup.setOnTagClickListener(new TagView.OnTagClickListener() {
@Override
public void onTagLongClick(String text) {
Log.w("MainActivity", text);
Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT)
.show();
}
@Override
public void onTagClick(String text) {
Log.e("MainActivity", text);
Toast.makeText(MainActivity.this, "长点击:" + text,
Toast.LENGTH_SHORT).show();
}
});
}
}

               add与clear的监听事件:

先在TagView中实现监听器接口OnTagClickListener,并对外提供方法来设置监听器,其实和大部分设置监听器一个样。然后给TagView设置OnClickListener和OnLongClickListener,并来执行OnTagClickListener回调方法。

                

public OnTagClickListener getTagClickListener() {
return mTagClickListener;
}
public void setTagClickListener(OnTagClickListener tagClickListener) {
mTagClickListener = tagClickListener;
}
/**
* 点击监听器
*/
public interface OnTagClickListener{
void onTagClick(String text);
void onTagLongClick(String text);
}
/**
* 初始化
* @param context
*/
private void _init(Context context) {
// 略......
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mTagClickListener != null) {
mTagClickListener.onTagClick(String.valueOf(mTagText));
}
}
});
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mTagClickListener != null) {
mTagClickListener.onTagLongClick(String.valueOf(mTagText));
}
return true;
}
});
}

                  现在要做的就是通过TagGroup来对外提供OnTagClickListener的设置接口,但是有一点要注意的是,如果你先添加Tags再设置监听器就可能出现前面设置的Tags没办法响应点击,所以你需要在设置监听器的地方为前面设置的Tags都重新添加上监听器,当然了你需要在之前保存好设置过的TagView。

                 

                             到此关于Android的流式布局的例子就写的差不多了,我其中也借鉴了其他大神的文章。共勉,我也要下班了,饭还没吃,饿死了。


     

最后

以上就是淡淡小虾米为你收集整理的Android流式标签布局,自定义标签控件tagView的全部内容,希望文章能够帮你解决Android流式标签布局,自定义标签控件tagView所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部