效果图
item 布局文件kingoit_flow_layout
<RelativeLayout
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:layout_gravity="center_vertical"
android:paddingBottom="@dimen/margin5"
android:paddingEnd="@dimen/margin5"
android:paddingRight="@dimen/margin10"
tools:ignore="RtlSymmetry">
<TextView
android:id="@+id/value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:paddingBottom="2dp"
android:paddingTop="2dp"
android:textColor="#000000"
android:textSize="16sp"/>
<ImageView
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/value"
android:layout_alignEnd="@id/value"
android:layout_alignRight="@id/value"
android:layout_alignTop="@+id/value"
android:layout_marginEnd="-2dp"
android:layout_marginRight="-2dp"
android:src="@drawable/cancel"
android:tint="@color/colorDialogContent"
android:visibility="gone"/>
RelativeLayout>
设置属性 res\values\attrs.xml
<declare-styleable name="KingoitFlowLayout">
<attr name="flowLayoutRadius" format="dimension"/>
<attr name="flowLayoutTextColor" format="color"/>
<attr name="flowLayoutTextColorSelector" format="color"/>
<attr name="flowLayoutTextSize" format="dimension"/>
<attr name="flowLayoutLineColor" format="color"/>
<attr name="flowLayoutLineWidth" format="dimension"/>
<attr name="flowLayoutBackgroundColor" format="color"/>
<attr name="flowLayoutBackgroundColorSelector" format="color"/>
<attr name="flowLayoutDeleteBtnColor" format="color"/>
declare-styleable>
控件实现 KingoitFlowLayout
/**
* kingoit,流式布局
* 20180815-修复不可滑动问题
* @author zuo
* @date 2018/7/16 11:48
*/
public class KingoitFlowLayout extends ViewGroup {
private List mChildPos = new ArrayList();
private float textSize;
private int textColor;
private int textColorSelector;
private float shapeRadius;
private int shapeLineColor;
private int shapeBackgroundColor;
private int shapeBackgroundColorSelector;
private float shapeLineWidth;
private int deleteBtnColor;
/**
* 是否是可删除模式
*/
private boolean isDeleteMode;
/**
* 记录所有选中着的词
*/
private List mAllSelectedWords = new ArrayList<>();
private class ChildPos {
int left, top, right, bottom;
public ChildPos(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
}
public KingoitFlowLayout(Context context) {
this(context, null);
}
public KingoitFlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
initAttributes(context, attrs);
}
/**
* 最终调用这个构造方法
*
* @param context 上下文
* @param attrs xml属性集合
* @param defStyle Theme中定义的style
*/
public KingoitFlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* 流式布局属性设置
*
* @param context
* @param attrs
*/
@SuppressLint("ResourceAsColor")
private void initAttributes(Context context, AttributeSet attrs) {
@SuppressLint("Recycle")
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.KingoitFlowLayout);
textSize = typedArray.getDimension(R.styleable.KingoitFlowLayout_flowLayoutTextSize, 16);
textColor = typedArray.getColor(R.styleable.KingoitFlowLayout_flowLayoutTextColor, Color.parseColor("#FF4081"));
textColorSelector = typedArray.getResourceId(R.styleable.KingoitFlowLayout_flowLayoutTextColorSelector, 0);
shapeRadius = typedArray.getDimension(R.styleable.KingoitFlowLayout_flowLayoutRadius, 40f);
shapeLineColor = typedArray.getColor(R.styleable.KingoitFlowLayout_flowLayoutLineColor, Color.parseColor("#ADADAD"));
shapeBackgroundColor = typedArray.getColor(R.styleable.KingoitFlowLayout_flowLayoutBackgroundColor, Color.parseColor("#c5cae9"));
shapeBackgroundColorSelector = typedArray.getResourceId(R.styleable.KingoitFlowLayout_flowLayoutBackgroundColorSelector, 0);
shapeLineWidth = typedArray.getDimension(R.styleable.KingoitFlowLayout_flowLayoutLineWidth, 4f);
deleteBtnColor = typedArray.getColor(R.styleable.KingoitFlowLayout_flowLayoutDeleteBtnColor, Color.GRAY);
}
/**
* 测量宽度和高度
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = 0, height = 0;
int lineWidth = 0, lineHeight = 0;
int count = getChildCount();
mChildPos.clear();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (lineWidth + childWidth > widthSize - getPaddingLeft() - getPaddingRight()) {
width = Math.max(width, lineWidth);
height += lineHeight;
lineWidth = childWidth;
lineHeight = childHeight;
mChildPos.add(new ChildPos(
getPaddingLeft() + lp.leftMargin,
getPaddingTop() + height + lp.topMargin,
getPaddingLeft() + childWidth - lp.rightMargin,
getPaddingTop() + height + childHeight - lp.bottomMargin));
} else {
mChildPos.add(new ChildPos(
getPaddingLeft() + lineWidth + lp.leftMargin,
getPaddingTop() + height + lp.topMargin,
getPaddingLeft() + lineWidth + childWidth - lp.rightMargin,
getPaddingTop() + height + childHeight - lp.bottomMargin));
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == count - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
int flowLayoutWidth = widthMode == MeasureSpec.AT_MOST ? width + getPaddingLeft() + getPaddingRight() : widthSize;
int flowLayoutHeight = heightMode == MeasureSpec.AT_MOST ? height + getPaddingTop() + getPaddingBottom() : heightSize;
realHeight = height + getPaddingTop() + getPaddingBottom();
measuredHeight = heightSize;
if (heightMode == MeasureSpec.EXACTLY) {
realHeight = Math.max(measuredHeight, realHeight);
}
scrollable = realHeight > measuredHeight;
setMeasuredDimension(flowLayoutWidth, flowLayoutHeight);
}
/**
* 让ViewGroup能够支持margin属性
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
/**
* 设置每个View的位置
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
ChildPos pos = mChildPos.get(i);
child.layout(pos.left, pos.top, pos.right, pos.bottom);
}
}
public void addItemView(LayoutInflater inflater, String tvName) {
View view = inflater.inflate(R.layout.kingoit_flow_layout, this, false);
ImageView delete = view.findViewById(R.id.delete);
if (isDeleteMode) {
delete.setVisibility(VISIBLE);
} else {
delete.setVisibility(GONE);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
delete.setImageTintList(ColorStateList.valueOf(deleteBtnColor));
}
TextView textView = view.findViewById(R.id.value);
textView.setTextSize(textSize / getContext().getResources().getDisplayMetrics().scaledDensity);
if (textColorSelector != 0) {
ColorStateList csl = getResources().getColorStateList(textColorSelector);
textView.setTextColor(csl);
} else {
textView.setTextColor(textColor);
}
textView.setPadding(20, 4, 20, 4);
textView.setText(tvName);
GradientDrawable drawable = new GradientDrawable();
drawable.setCornerRadius(shapeRadius);
drawable.setStroke((int) shapeLineWidth, shapeLineColor);
if (shapeBackgroundColorSelector != 0) {
ColorStateList csl = getResources().getColorStateList(shapeBackgroundColorSelector);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
drawable.setColor(csl);
}
} else {
drawable.setColor(shapeBackgroundColor);
}
textView.setBackgroundDrawable(drawable);
this.addView(view);
}
public boolean isDeleteMode() {
return isDeleteMode;
}
public void setDeleteMode(boolean deleteMode) {
isDeleteMode = deleteMode;
}
private boolean scrollable;
private int measuredHeight;
private int realHeight;
private int scrolledHeight = 0;
private int startY;
private int offsetY;
private boolean pointerDown;
/**
* 滚动事件的处理,当布局可以滚动(内容高度大于测量高度)时,对手势操作进行处理
*/
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (scrollable && isInterceptedTouch) {
int currY = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if (!pointerDown) {
startY = currY;
pointerDown = true;
} else {
offsetY = startY - currY;
this.scrollTo(0, scrolledHeight + offsetY);
}
break;
case MotionEvent.ACTION_UP:
scrolledHeight += offsetY;
if (scrolledHeight + offsetY < 0) {
this.scrollTo(0, 0);
scrolledHeight = 0;
} else if (scrolledHeight + offsetY + measuredHeight > realHeight) {
this.scrollTo(0, realHeight - measuredHeight);
scrolledHeight = realHeight - measuredHeight;
}
pointerDown = false;
break;
default:
break;
}
}
return super.onTouchEvent(event);
}
/**
* 事件拦截,当手指按下或抬起的时候不进行拦截(因为可能这个操作只是点击了布局中的某个子元素);
* 当手指移动的时候,才将事件拦截;
* 因增加最小滑动距离防止点击时误触滑动
*/
private boolean isInterceptedTouch;
private int startYY = 0;
private boolean pointerDownY;
private int minDistance = 10;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int currY = (int) ev.getY();
int offsetY = 0;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
pointerDownY = true;
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (pointerDownY) {
startYY = currY;
} else {
offsetY = currY - startYY;
}
pointerDownY = false;
intercepted = Math.abs(offsetY) > minDistance;
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
isInterceptedTouch = intercepted;
return intercepted;
}
/**
* 流式布局显示
* Toast.makeText(FlowLayoutActivity.this, keywords, Toast.LENGTH_SHORT).show();
*
* @param list
*/
public void showTag(final List list, final ItemClickListener listener) {
removeAllViews();
for (int i = 0; i < list.size(); i++) {
final String keywords = list.get(i);
addItemView(LayoutInflater.from(getContext()), keywords);
final int finalI = i;
getChildAt(i).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (isDeleteMode()) {
list.remove(keywords);
showTag(list, listener);
} else {
View child = getChildAt(finalI);
child.setSelected(!child.isSelected());
if (child.isSelected()) {
mAllSelectedWords.add(list.get(finalI));
} else {
mAllSelectedWords.remove(list.get(finalI));
}
listener.onClick(keywords, mAllSelectedWords);
}
}
});
}
}
public interface ItemClickListener {
/**
* item 点击事件
*
* @param currentSelectedkeywords
* @param allSelectedKeywords
*/
void onClick(String currentSelectedkeywords, List allSelectedKeywords);
}
}
控件使用
"@+id/kingoit_flow_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
app:flowLayoutBackgroundColor="@color/light_blue_700"
app:flowLayoutBackgroundColorSelector="@drawable/selector_flowlayout_item_bg"
app:flowLayoutDeleteBtnColor="@color/colorPrimary"
app:flowLayoutLineColor="@color/transation"
app:flowLayoutLineWidth="1dp"
app:flowLayoutRadius="50dp"
app:flowLayoutTextColor="@color/light_blue_50"
app:flowLayoutTextColorSelector="@drawable/selector_flowlayout_item_text_color"
app:flowLayoutTextSize="16sp"/>
/**
* 流式布局使用示例代码
* @author zuo
* @date 2018/8/15 9:39
*/
public class FlowTestActivity extends Activity implements KingoitFlowLayout.ItemClickListener {
private KingoitFlowLayout flowLayout;
private KingoitHeadView headView;
private List list = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flow_test);
headView = findViewById(R.id.head_view);
flowLayout = findViewById(R.id.kingoit_flow_layout);
initData();
initView();
}
private void initData() {
for (int i = 0; i < 10; i++) {
list.add("战争女神");
list.add("蒙多");
list.add("德玛西亚皇子");
list.add("殇之木乃伊");
list.add("狂战士");
list.add("布里茨克拉克");
list.add("冰晶凤凰 艾尼维亚");
list.add("德邦总管");
list.add("野兽之灵 乌迪尔 (德鲁伊)");
list.add("赛恩");
list.add("诡术妖姬");
list.add("永恒梦魇");
}
}
private void initView() {
headView.getHeadRightImg().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
flowLayout.setDeleteMode(!flowLayout.isDeleteMode());
flowLayout.showTag(list, FlowTestActivity.this);
}
});
flowLayout.showTag(list, FlowTestActivity.this);
}
@Override
public void onClick(String currentSelectedkeywords, List allSelectedKeywords) {
Toast.makeText(FlowTestActivity.this, currentSelectedkeywords, Toast.LENGTH_SHORT).show();
}
}
- 样式代码
selector_flowlayout_item_bg
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/light_blue_50" android:state_pressed="true" />
<item android:color="@color/colorWhite" android:state_selected="true" />
<item android:color="@color/light_blue_700" />
selector>
selector_flowlayout_item_text_color
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/light_blue_700" android:state_pressed="true" />
<item android:color="@color/light_blue_700" android:state_selected="true" />
<item android:color="@color/light_blue_50" />
selector>