一、简 介
由于移动互联网的发展,大数据技术的进步,app厂商或者犯罪分子会推广你喜欢的内容或者广告。对于各种类型的需求,通过关系数据库心亦不能满足需要,这种情况通过nosql数据库来存储用户兴趣。对于用户兴趣的标注,就是通过tag或者cookie等实现。在这方面,客户端需求也是很大,无论是供应链、内容还是地理定位,都有很大的需求。
这种标记方式比较常用
二、代码实现
public class TagFlowLayout extends ViewGroup {
private final static String TAG = "TagFlowLayout";
//自定义属性
private int spacingBetweenItems;
private int lineSpacing;
private OnItemSelectedListener mOnItemSelectedListener;
private OnItemRemovedlistener mOnItemRemovedListener;
public TagFlowLayout(Context context) {
this(context, null);
}
public TagFlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TagFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout);
spacingBetweenItems = ta.getDimensionPixelSize(R.styleable.TagFlowLayout_leftAndRightSpace, 10);
lineSpacing = ta.getDimensionPixelSize(R.styleable.TagFlowLayout_rowSpace, 10);
ta.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//为所有的标签childView计算宽和高
measureChildren(widthMeasureSpec, heightMeasureSpec);
//获取高的模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//建议的高度
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//布局的宽度采用建议宽度(match_parent或者size),如果设置wrap_content也是match_parent的效果
int width = MeasureSpec.getSize(widthMeasureSpec);
int height;
if (heightMode == MeasureSpec.EXACTLY) {
//如果高度模式为EXACTLY(match_perent或者size),则使用建议高度
height = heightSize;
} else {
//其他情况下(AT_MOST、UNSPECIFIED)需要计算计算高度
int childCount = getChildCount();
if (childCount <= 0) {
height = 0; //没有标签时,高度为0
} else {
int row = 1; // 标签行数
int widthSpace = width;// 当前行右侧剩余的宽度
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
//获取标签宽度
int childW = view.getMeasuredWidth();
Log.v(TAG, "标签宽度:" + childW + " 行数:" + row + " 剩余宽度:" + widthSpace);
if (widthSpace >= childW) {
//如果剩余的宽度大于此标签的宽度,那就将此标签放到本行
widthSpace -= childW;
} else {
row++; //增加一行
//如果剩余的宽度不能摆放此标签,那就将此标签放入一行
widthSpace = width - childW;
}
//减去标签左右间距
widthSpace -= spacingBetweenItems;
}
//由于每个标签的高度是相同的,所以直接获取第一个标签的高度即可
int childH = getChildAt(0).getMeasuredHeight();
//最终布局的高度=标签高度*行数+行距*(行数-1)
height = (childH * row) + lineSpacing * (row - 1);
Log.v(TAG, "总高度:" + height + " 行数:" + row + " 标签高度:" + childH);
}
}
//设置测量宽度和测量高度
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int row = 0;
int right = 0; // 标签相对于布局的右侧位置
int botom; // 标签相对于布局的底部位置
if(getChildCount()<=0) return;
final List<List<View>> mGroupViews = new ArrayList<>();
List<View> rowsView = new ArrayList<>();
mGroupViews.add(rowsView);
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childW = childView.getMeasuredWidth();
right += childW;
if (right > (r - l)) {
right = childW;
rowsView = new ArrayList<>();
mGroupViews.add(rowsView);
}
right += spacingBetweenItems;
rowsView.add(childView);
}
for (List<View> lineViews : mGroupViews){
int size = lineViews.size();
int maxWidth = r-l;
int maxLineViewsWidth = getLineChildrenSumWidth(lineViews);
int dxWidth = maxWidth - maxLineViewsWidth-(size-1)*spacingBetweenItems;
right = Math.max(0,dxWidth/2);
for (int i=0;i<size;i++){
View childView = lineViews.get(i);
int childW = childView.getMeasuredWidth();
int childH = childView.getMeasuredHeight();
//右侧位置=本行已经占有的位置+当前标签的宽度
right += childW;
//底部位置=已经摆放的行数*(标签高度+行距)+当前标签高度
botom = row * (childH + lineSpacing) + childH;
childView.layout(right - childW, botom - childH, right, botom);
right += spacingBetweenItems;
}
row++;
}
}
private int getLineChildrenSumWidth(List<View> lineViews) {
if(lineViews==null) return 0;
int width = 0;
for (int i=0;i<lineViews.size();i++){
View childView = lineViews.get(i);
width += childView.getMeasuredWidth();
}
return width;
}
private final ArrayList<TagBean> tags = new ArrayList<>();
public void setTags(ArrayList<TagBean> tags) {
if (tags == null || tags.isEmpty()) return;
this.tags.clear();
this.tags.addAll(tags);
for (TagBean tag : this.tags) {
addTag(tag,false);
}
}
public void addTag(TagBean tagBean){
addTag(tagBean,true);
}
public void addTag(TagBean tag,boolean addTag) {
if(addTag){
this.tags.add(tag);
}
final LayoutInflater inflater = LayoutInflater.from(getContext());
final View view = inflater.inflate(R.layout.item_tag, this, false);
view.setSelected(tag.isSelected());
TextView text = view.findViewById(R.id.text);
text.setText(tag.getContent());
view.setTag(tag);
handleViewClick(view);
this.addView(view);
}
private void handleViewClick(View view) {
if (view == null || view.getTag() == null) return;
TagBean tag = ((TagBean) view.getTag());
if (tag.isShowClose()) {
View close = view.findViewById(R.id.close);
close.setVisibility(VISIBLE);
close.setOnClickListener((v) -> {
removeTag(tag);
if (mOnItemRemovedListener!= null){
this.mOnItemRemovedListener.onItemRemoved(tag.getContent());
}
});
} else {
view.setOnClickListener((v) -> {
tag.setSelected(!tag.isSelected());
view.setSelected(tag.isSelected());
if(mOnItemSelectedListener!=null){
this.mOnItemSelectedListener.onItemSelectedChanged(tag.isSelected(),tag,view);
}
});
}
}
public void removeTag(TagBean tag) {
this.tags.remove(tag);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childAt = getChildAt(i);
if (childAt == null)
continue;
Object childTag = childAt.getTag();
if (childTag == null)
continue;
if (childTag.equals(tag)) {
this.removeViewAt(i);
break;
}
}
}
public ArrayList<String> getSelectedTags() {
if (tags.isEmpty()) return null;
final ArrayList<String> list = new ArrayList<>();
for (TagBean tag : this.tags) {
if (!tag.isSelected())
continue;
list.add(tag.getContent());
}
return list;
}
public ArrayList<String> getAllTags() {
if (tags.isEmpty()) return null;
final ArrayList<String> list = new ArrayList<>();
for (TagBean tag : this.tags) {
list.add(tag.getContent());
}
return list;
}
public static class TagBean {
private String content;
private boolean selected;
private boolean showClose;
public TagBean(String content, boolean selected, boolean showClose) {
this.content = content;
this.selected = selected;
this.showClose = showClose;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public boolean isShowClose() {
return showClose;
}
public void setShowClose(boolean showClose) {
this.showClose = showClose;
}
@Override
public String toString() {
return "TagBean{" +
"content='" + content + '\'' +
", selected=" + selected +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TagBean tagBean = (TagBean) o;
return selected == tagBean.selected &&
showClose == tagBean.showClose &&
Objects.equals(content, tagBean.content);
}
@Override
public int hashCode() {
return Objects.hash(content, selected, showClose);
}
}
public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
this.mOnItemSelectedListener = onItemSelectedListener;
}
public interface OnItemSelectedListener{
public void onItemSelectedChanged(boolean isSelected,TagBean bean,View tagView);
}
public void setOnItemRemovedlistener(OnItemRemovedlistener onItemRemovedlistener){
this.mOnItemRemovedListener = onItemRemovedlistener;
}
public interface OnItemRemovedlistener{
public void onItemRemoved(String s);
}
}
属性定义
<declare-styleable name="TagFlowLayout">
<!--标签之间左右距离-->
<attr name="leftAndRightSpace" format="dimension" />
<!--标签行距-->
<attr name="rowSpace" format="dimension" />
</declare-styleable>
item_tag.xml布局定义
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tag_outline_selector"
android:gravity="center"
android:orientation="horizontal"
android:paddingLeft="15dp"
android:paddingTop="8dp"
android:paddingRight="12dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/tag_textcolor_selector"
android:textSize="12sp"
tools:text="Payment"
android:maxLines="1"
android:ellipsize="end"
/>
<ImageView
android:id="@+id/close"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginLeft="5dp"
android:paddingLeft="3dp"
android:paddingRight="3dp"
android:src="@drawable/ic_close"
android:visibility="gone"
tools:visibility="visible"
/>
</LinearLayout>
tag_outline_selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape>
<corners android:radius="10000dip" />
<solid android:color="@android:color/white" />
<stroke android:width="1dp" android:color="@color/photo_detail_restore_color" />
</shape>
</item>
<item android:state_selected="false">
<shape>
<corners android:radius="10000dip" />
<solid android:color="@android:color/white" />
<stroke android:width="1dp" android:color="#bfbfbf" />
</shape>
</item>
</selector>
效果一
效果二
来源:oschina
链接:https://my.oschina.net/ososchina/blog/3182774