自定义View,一是为了满足设计需求,二是开发者进阶的标志之一。随心所欲就是我等奋斗的目标!!!
效果
实现逻辑
-
明确需求
1、标签流效果;
2、可以动态添加标签;
3、标签需要有点击效果以及回调; -
整理思路
既然要装载标签,就需要自定义ViewGroup ,而自定义ViewGroup 比较复杂的就是onLayout()中对子View的排版。既然是标签,在一行中一定要显示完整,在排版的时候注意这一点,需要添加判断!其次,标签要有点击事件,这里的实现我们可以借助子View的点击事件封装一个接口,实现我们自己的点击事件!
-
要点
1、对于子View的测量;
2、对于子View的排版; -
Ps: 需要注意的是给子View设置背景Drawable的时候不可以设置同一个Drawable,否则会出现错乱情况!见
getSonView()
方法中
完整代码
/**
* 自定义ViewGroup实现标签流效果
*
* @attr customInterval //标签之间的间隔
* @attr customSelectMode //标签选项模式
* @attr customSonBackground //标签背景
* @attr customSonPaddingBottom //标签底部内边距
* @attr customSonPaddingLeft //标签左边内边距
* @attr customSonPaddingRight //标签右边内边距
* @attr customSonPaddingTop //标签顶部内边距
* @attr customSonTextColor //标签文字颜色
* @attr customSonTextSize //标签文字尺寸
*/
public class CustomLableView extends ViewGroup {
private static final String TAG = "CustomLableView";
private int customInterval = 15;
private int customSonPaddingLeft = 20;
private int customSonPaddingRight = 20;
private int customSonPaddingTop = 10;
private int customSonPaddingBottom = 10;
private Drawable customSonBackground = null;
private float customSonTextSize = 0;
private ColorStateList customSonTextColor = ColorStateList.valueOf(0xFF000000);
private ArrayList<String> mSonTextContents = new ArrayList<>();
private ArrayList<TextView> mSonTextViews = new ArrayList<>();
private Context mContext = null;
private OnItemClickListener mOnItemClickListener;
private int customSelectMode;
public CustomLableView(Context context) {
this(context, null);
}
public CustomLableView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomLableView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
//初始化自定义属性
initAttrs(context, attrs);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = customInterval;
int top = customInterval;
int mMeasuredWidth = this.getMeasuredWidth();
//防止重复添加
CustomLableView.this.removeAllViews();
for (int i = 0; i < mSonTextViews.size(); i++) {
final TextView mTextView = mSonTextViews.get(i);
//将当前子View添加到ViewGroup中
CustomLableView.this.addView(mTextView);
//获取当前子View的宽高
int measuredHeight = mTextView.getMeasuredHeight();
int measuredWidth = mTextView.getMeasuredWidth();
//判断一行是否可显示
if ((mMeasuredWidth - left) >= (measuredWidth + customInterval * 2)) {//一行可显示
/**
* 1、(mMeasuredWidth - left) X轴剩余空间
* 2、(measuredWidth + customInterval * 2) 当前子View和间隔需要的空间
*/
mTextView.layout(left, top, left + measuredWidth, top + measuredHeight);
left += (measuredWidth + customInterval);
} else {//需要换行显示
//还原X轴的起始位置
left = customInterval;
//Y轴高度增加
top += (measuredHeight + customInterval);
mTextView.layout(left, top, left + measuredWidth, top + measuredHeight);
left += (measuredWidth + customInterval);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//控件宽度
int mMeasureViewWidht = MeasureSpec.getSize(widthMeasureSpec);
//显示行数
int line = 1;
//每行当前宽度
int lineWidht = customInterval;
//每行高度(子View的高度)
int mSonMeasuredHeight = 0;
mSonTextViews.clear();
for (int i = 0; i < mSonTextContents.size(); i++) {
TextView mSonView = getSonView(i, mSonTextContents.get(i));
//对子View进行测量
mSonView.measure(0, 0);
//获取子View的测量尺寸
int mSonMeasuredWidth = mSonView.getMeasuredWidth() + customInterval;
mSonMeasuredHeight = mSonView.getMeasuredHeight() + customInterval;
//添加到数组中
mSonTextViews.add(mSonView);
if (mMeasureViewWidht >= mSonMeasuredWidth) {
if ((mMeasureViewWidht - lineWidht) >= mSonMeasuredWidth) {
lineWidht += mSonMeasuredWidth;
} else {
//行数自加1
line += 1;
lineWidht = customInterval + mSonMeasuredWidth;
}
} else {
mSonTextViews.clear();
setMeasuredDimension(0, 0);
return;
}
}
//设置控件尺寸
setMeasuredDimension(mMeasureViewWidht, mSonMeasuredHeight * line + customInterval);
}
/**
* 设置标签内容集合
*
* @param sonContent 标签内容
*/
public void setSonContent(ArrayList<String> sonContent) {
if (sonContent != null) {
mSonTextContents.clear();
mSonTextContents.addAll(sonContent);
requestLayout();
}
}
/**
* 添加一个标签
*
* @param sonContent 标签内容
*/
public void addSonContent(String sonContent) {
if (!TextUtils.isEmpty(sonContent)) {
mSonTextContents.add(0, sonContent);
requestLayout();
}
}
/**
* 设置标签点击事件
*
* @param onItemClickListener 回调接口
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
/**
* 获取子View
*
* @return TextView
*/
private TextView getSonView(final int i, final String content) {
if (mContext != null) {
TextView mTextView = new TextView(mContext);
mTextView.setTextColor(customSonTextColor);
mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, customSonTextSize);
//不可以设置相同的Drawable
mTextView.setBackgroundDrawable(customSonBackground.getConstantState().newDrawable());
mTextView.setText(content);
mTextView.setPadding(customSonPaddingLeft, customSonPaddingTop, customSonPaddingRight, customSonPaddingBottom);
//消除TextView默认的上下内边距
mTextView.setIncludeFontPadding(false);
mTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//选择模式
if (customSelectMode != 102) {
for (int j = 0; j < mSonTextViews.size(); j++) {
mSonTextViews.get(j).setSelected(false);
}
v.setSelected(true);
} else {
v.setSelected(true);
}
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(v, i, content);
}
}
});
return mTextView;
}
return null;
}
/**
* 初始化自定义属性
*
* @param context
* @param attrs
*/
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomLableView);
customSonBackground = mTypedArray.getDrawable(R.styleable.CustomLableView_customSonBackground);
customInterval = (int) mTypedArray.getDimension(R.styleable.CustomLableView_customInterval, customInterval);
customSonPaddingLeft = (int) mTypedArray.getDimension(R.styleable.CustomLableView_customSonPaddingLeft, customSonPaddingLeft);
customSonPaddingRight = (int) mTypedArray.getDimension(R.styleable.CustomLableView_customSonPaddingRight, customSonPaddingRight);
customSonPaddingTop = (int) mTypedArray.getDimension(R.styleable.CustomLableView_customSonPaddingTop, customSonPaddingTop);
customSonPaddingBottom = (int) mTypedArray.getDimension(R.styleable.CustomLableView_customSonPaddingBottom, customSonPaddingBottom);
customSonTextSize = (int) mTypedArray.getDimension(R.styleable.CustomLableView_customSonTextSize, 0);
customSonTextColor = mTypedArray.getColorStateList(R.styleable.CustomLableView_customSonTextColor);
customSelectMode = mTypedArray.getInt(R.styleable.CustomLableView_customSelectMode, 101);
if (customSonTextSize == 0) {
customSonTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, getResources().getDisplayMetrics());
}
mTypedArray.recycle();
}
public interface OnItemClickListener {
void onItemClick(View view, int position, String sonContent);
}
}
- 自定义属性
<declare-styleable name="CustomLableView">
<attr name="customSonBackground" format="reference" />
<attr name="customInterval" format="dimension" />
<attr name="customSonPaddingLeft" format="dimension" />
<attr name="customSonPaddingRight" format="dimension" />
<attr name="customSonPaddingTop" format="dimension" />
<attr name="customSonPaddingBottom" format="dimension" />
<attr name="customSonTextSize" format="dimension" />
<attr name="customSonTextColor" format="color" />
<attr name="customSelectMode">
<enum name="alone" value="101" />
<enum name="multi" value="102" />
</attr>
</declare-styleable>