編輯:關於Android編程
我們在一些項目中會用到自定義流式布局,我個人覺得流式布局將呆板的布局錯綜排列,來提升用戶體驗度.(還可以不辜負美工妹子們的期望,人家畢竟也辛辛苦苦設計半天)。今天終於有時間來做做了。寫的不好,很多地方值得改進望大家一起交流。
這是效果圖:

實現基本功能:
首先來說明幾點:
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());
}
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();
}
});
}
}
先在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;
}
});
}
到此關於Android的流式布局的例子就寫的差不多了,我其中也借鑒了其他大神的文章。共勉,我也要下班了,飯還沒吃,餓死了。
Android studio常用快捷鍵
ctrl+shift+N 查找文件,以懸浮窗口的形式搜索 contrl+N 查找類,與ctrl+shift+N相似,但是只能查找類 ctrl + E 最近打開的文件,可
Android應用性能測試
對於Web網頁來說,頁面的訪問、加載速度對於用戶體驗來說是很重要的,而如果把Android中的每個Activity都看成是一個頁面的話,Activity的啟動速度憑主觀的
關於tomcat中Servlet對象池
Servlet在不實現SingleThreadModel的情況下運行時是以單個實例模式,如下圖,這種情況下,Wrapper容器只會通過反射實例化一個Servlet對象,對
Android自定義dialog中的EditText無法彈出鍵盤的解決
最近我獨立開發的項目《全醫會》已經在內測當中了,很快將會上架到各大應用市場。之前開發的幾個項目都因為一些原因沒有上架還是比較遺憾的。所以,最近我心情格外的好。 今天在做