之前寫了一篇折疊流式布局,bug有點(diǎn)多,也不好改,究其原因就是寫的邏輯太多,改起來不方便,畢竟主體邏輯不是自己寫的,基于別人的改總是怪怪的。那么,我就想想這個(gè)東西的難點(diǎn)在哪?有什么簡(jiǎn)單的方法解決?
難點(diǎn)
我們的需求是:流式布局展示,當(dāng)數(shù)量沒超過兩行,那么就不加入展開與收起按鈕,如果超過兩行但小于等于4行,在收起狀態(tài)時(shí)加入展開按鈕,在展開狀態(tài)展示收起按鈕,如果超過4行,在收起狀態(tài)時(shí)加入展開按鈕,在展開狀態(tài)最大4行的最后展示收起按鈕。
折疊一個(gè)流式布局,在于加入一個(gè)子view的時(shí)候,要提前知道折疊的位置。
比如流式布局在折疊狀態(tài)時(shí),加入一個(gè)子view后,我們要知道他有沒有超過兩行,超過了,我們需要知道第二行的最后一個(gè)index是多少,然后在這個(gè)位置插入向下按鈕。
再比如,流式布局不在折疊狀態(tài)時(shí),加入一個(gè)子view,我們要判斷他是否在1行到4行之間,如果在,那么他后面一定要加一個(gè)向上按鈕,因?yàn)樗钦归_的,一定要有一個(gè)收起按鈕。如果超過4行,那么我們需要知道第4行最后一個(gè)按鈕。
當(dāng)然開發(fā)中還發(fā)現(xiàn)一個(gè)問題,那就是加入一個(gè)子view后當(dāng)前剛好是展開狀態(tài)的第4行,那么加入收起按鈕的時(shí)候,我們需要判斷當(dāng)前剩余的寬度夠不夠我們加入向上按鈕,夠的話,我們index插入 位置直接返回所有子view的大小,如果不能,那么我們返回所有所有子view的大小 -1 。因?yàn)槿绻覀冞€返回所有子view的大小的話,就會(huì)排到第5行。
思路
怎么提前知道要插入的位置呢?前一篇文章是用一個(gè)view,在onMeasure里面寫了一大堆邏輯去寫。現(xiàn)在換一個(gè)思路,我們做兩個(gè)view,裝在一個(gè)布局里面,一個(gè)view(A)是專門用于計(jì)算插入位置,另一個(gè)view(B)是專門展示數(shù)據(jù)。當(dāng)A加入所有的子view后,我們能很快的知道我們需要的index,加入這個(gè)index為7,那么在B里面我們就只要裝0到6的子view,最后7就變成收起或展開按鈕。A就是我們的替身。缺點(diǎn)就是如果子view很大很大,那么就會(huì)超市或者很慢。
代碼
包裹兩個(gè)子view的布局FlowContentLayout
public class FlowContentLayout extends RelativeLayout {
private FlowLayout mBackFlowLayout;
private int mLastIndex = 0;
private FlowLayout mFontFlowLayout;
private List<String> list = new ArrayList<>();
private View upView;
private View downView;
public FlowContentLayout(Context context) {
this(context,null);
}
public FlowContentLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public FlowContentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.flow_content_layout,this);
upView = LayoutInflater.from(context).inflate(R.layout.view_item_fold_up, this, false);
upView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mBackFlowLayout.setFoldState(true);
mFontFlowLayout.setFoldState(true);
refreshViews();
}
});
downView = LayoutInflater.from(context).inflate(R.layout.view_item_fold_down, this, false);
downView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mBackFlowLayout.setFoldState(false);
mFontFlowLayout.setFoldState(false);
refreshViews();
}
});
mBackFlowLayout = findViewById(R.id.mFlowLayout);
mBackFlowLayout.setFlowContentLayout(this);
mFontFlowLayout = findViewById(R.id.mFontFlowLayout);
mFontFlowLayout.setUpFoldView(upView);
mFontFlowLayout.setDownFoldView(downView);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mBackFlowLayout.setFlowContentLayout(null);
}
/**
* 這里把隱藏的幕后計(jì)算布局加入view先計(jì)算
* @param list
*/
public void addViews(@NotNull List<String> list) {
mLastIndex = 0;
this.list.clear();
this.list.addAll(list);
mBackFlowLayout.addViews(list);
}
/**
* 相同的數(shù)據(jù)重新刷新
*/
private void refreshViews(){
if(list != null && list.size() > 0){
mLastIndex = 0;
mBackFlowLayout.addViews(list);
}
}
/**
* 幕后布局計(jì)算后的最大折疊位置
* @param foldState
* @param index
* @param flag 是否需要加入向上或者向下按鈕
* @param lineWidthUsed
*/
public void foldIndex(boolean foldState, int index, boolean flag, int lineWidthUsed) {
if(mLastIndex != index){//防止多次調(diào)用
mLastIndex = index;
//添加外部真正的布局
if(flag){
List<String> list = new ArrayList<>();
for (int x = 0; x < index; x++) {
list.add(FlowContentLayout.this.list.get(x));
}
list.add("@@");
mFontFlowLayout.addViews(list);
}else{
List<String> list = new ArrayList<>();
for (int x = 0; x < FlowContentLayout.this.list.size(); x++) {
list.add(FlowContentLayout.this.list.get(x));
}
mFontFlowLayout.addViews(list);
}
}
}
public int getUpViewWidth() {
if(upView != null){
return Utils.getViewWidth(upView);
}
return 0;
}
/**
* 刪除全部后轉(zhuǎn)態(tài)恢復(fù)
*/
public void releaseState(){
mBackFlowLayout.setFoldState(true);
mFontFlowLayout.setFoldState(true);
}
}
流式布局FlowLayout
public class FlowLayout extends ViewGroup {
/**
* 水平距離
*/
private int mHorizontalSpacing = Utils.dp2px(8f);
private static final int MAX_LINE = 3;//從0開始計(jì)數(shù)
private static final int MIN_LINE = 1;//從0開始計(jì)數(shù)
private FlowContentLayout mFlowContentLayout;
private boolean foldState = true;
private View upFoldView;
private View downFoldView;
private int mWidth;
private int textViewHeight;
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setFlowContentLayout(FlowContentLayout mFlowContentLayout) {
this.mFlowContentLayout = mFlowContentLayout;
}
public void setFoldState(boolean foldState) {
this.foldState = foldState;
}
public void setUpFoldView(View upFoldView) {
this.upFoldView = upFoldView;
}
public void setDownFoldView(View downFoldView) {
this.downFoldView = downFoldView;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = getWidth();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取mode 和 size
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
//判斷如果布局寬度拋去左右padding小于0,也不能處理了
if (layoutWidth <= 0) {
return;
}
//這里默認(rèn)寬高默認(rèn)值默認(rèn)把左右,上下padding加上
int width = getPaddingLeft() + getPaddingRight();
int height = getPaddingTop() + getPaddingBottom();
//初始一行的寬度
int lineWidth = 0;
//初始一行的高度
int lineHeight = 0;
//測(cè)量子View
measureChildren(widthMeasureSpec, heightMeasureSpec);
int[] wh = null;
int childWidth, childHeight;
//行數(shù)
int line = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View view = getChildAt(i);
//這里需要先判斷子view是否被設(shè)置了GONE
if (view.getVisibility() == GONE) {
continue;
}
childWidth = view.getMeasuredWidth();
childHeight = view.getMeasuredHeight();
//第一行
if (i == 0) {
lineWidth = getPaddingLeft() + getPaddingRight() + childWidth;
lineHeight = childHeight;
} else {
//判斷是否需要換行
//換行
if (lineWidth + mHorizontalSpacing + childWidth > widthSize) {
line++;//行數(shù)增加
// 取最大的寬度
width = Math.max(lineWidth, width);
//重新開啟新行,開始記錄
lineWidth = getPaddingLeft() + getPaddingRight() + childWidth;
//疊加當(dāng)前高度,
height += lineHeight;
//開啟記錄下一行的高度
lineHeight = childHeight;
if(mFlowContentLayout != null){
if(foldState && line > MIN_LINE){
callBack(foldState,i-1, true,lineWidth);
break;
}else if(!foldState && line > MAX_LINE){
callBack(foldState,i-1, true,lineWidth);
break;
}
}
}
//不換行
else {
lineWidth = lineWidth + mHorizontalSpacing + childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
}
// 如果是最后一個(gè),則將當(dāng)前記錄的最大寬度和當(dāng)前l(fā)ineWidth做比較
if (i == count - 1) {
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
//根據(jù)計(jì)算的值重新設(shè)置
if(mFlowContentLayout == null){
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
heightMode == MeasureSpec.EXACTLY ? heightSize : height);
}else{
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
0);
}
if(foldState && (line >= 0 && line <= MIN_LINE)){
callBack(foldState,getChildCount(),false,lineWidth);
}
if(!foldState && (line >= 0 && line <= MAX_LINE)){
if(mFlowContentLayout != null){
int upViewWidth = mFlowContentLayout.getUpViewWidth() + mHorizontalSpacing;
if(lineWidth > (mWidth - upViewWidth) && line == MAX_LINE){
callBack(foldState,getChildCount() - 1,true,lineWidth);
}else{
callBack(foldState,getChildCount(),true,lineWidth);
}
}else{
callBack(foldState,getChildCount(),true,lineWidth);
}
}
}
/**
* 超過最大數(shù)的回調(diào)
* @param foldState
* @param index 最大數(shù)的位置。
* @param b
* @param lineWidthUsed
*/
private void callBack(boolean foldState, int index, boolean b, int lineWidthUsed) {
if(mFlowContentLayout != null){
mFlowContentLayout.foldIndex(foldState,index,b,lineWidthUsed);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int layoutWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
if (layoutWidth <= 0) {
return;
}
int childWidth, childHeight;
//需要加上top padding
int top = getPaddingTop();
final int[] wh = getMaxWidthHeight();
int lineHeight = 0;
int line = 0;
//左對(duì)齊
//左側(cè)需要先加上左邊的padding
int left = getPaddingLeft();
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View view = getChildAt(i);
//這里一樣判斷下顯示狀態(tài)
if (view.getVisibility() == GONE) {
continue;
}
//自適寬高
childWidth = view.getMeasuredWidth();
childHeight = view.getMeasuredHeight();
//第一行開始擺放
if (i == 0) {
view.layout(left, top, left + childWidth, top + childHeight);
lineHeight = childHeight;
} else {
//判斷是否需要換行
if (left + mHorizontalSpacing + childWidth > layoutWidth + getPaddingLeft()) {
line++;
//重新起行
left = getPaddingLeft();
top = top + lineHeight;
lineHeight = childHeight;
} else {
left = left + mHorizontalSpacing;
lineHeight = Math.max(lineHeight, childHeight);
}
view.layout(left, top, left + childWidth, top + childHeight);
}
//累加left
left += childWidth;
}
}
/**
* 取最大的子view的寬度和高度
*
* @return
*/
private int[] getMaxWidthHeight() {
int maxWidth = 0;
int maxHeight = 0;
for (int i = 0, count = getChildCount(); i < count; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {
continue;
}
maxWidth = Math.max(maxWidth, view.getMeasuredWidth());
maxHeight = Math.max(maxHeight, view.getMeasuredHeight());
}
return new int[]{maxWidth, maxHeight};
}
public void addViews(List<String> list){
removeAllViews();
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
for (int x = 0; x< list.size(); x++) {
String s = list.get(x);
if(TextUtils.equals("@@",s)){
if(foldState){
if(downFoldView != null){
Utils.removeFromParent(downFoldView);
addView(downFoldView,layoutParams);
}
}else{
if(upFoldView != null){
Utils.removeFromParent(upFoldView);
addView(upFoldView,layoutParams);
}
}
}else{
addTextView(s,layoutParams);
}
}
}
private void addTextView(String s,LinearLayout.LayoutParams layoutParams){
LinearLayout linearLayout = new LinearLayout(getContext());
linearLayout.setPadding(0,Utils.dp2px(8f),0,0);
linearLayout.setLayoutParams(layoutParams);
TextView tv = new TextView(getContext());
tv.setPadding(Utils.dp2px(12f), Utils.dp2px(8f), Utils.dp2px(12f), Utils.dp2px(8f));
tv.setText(s);
tv.setSingleLine();
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP,12);
tv.setTextColor(getResources().getColor(R.color.ff666666));
tv.setEllipsize(TextUtils.TruncateAt.END);
tv.setBackgroundResource(R.drawable.search_tag_bg);
linearLayout.addView(tv,new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
addView(linearLayout,layoutParams);
textViewHeight = Utils.getViewHeight(tv);
}
}
最后activity里面只要往里面加入String集合就行
mFlowContentLayout?.addViews(list)
當(dāng)需要清空所有數(shù)據(jù),重新加入數(shù)據(jù)時(shí)不止String集合需要清空,也需要調(diào)用FlowContentLayout的releaseState方法還原他的收起展開狀態(tài)。
flow_content_layout布局代碼填一下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.laiyifen.search2.flowLayout.FlowLayout
android:id="@+id/mFlowLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"/>
<com.laiyifen.search2.flowLayout.FlowLayout
android:id="@+id/mFontFlowLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
對(duì)了在實(shí)際運(yùn)用中,我把這個(gè)布局放在了列表的頭部,導(dǎo)致會(huì)調(diào)用他的detach方法,導(dǎo)致不能回調(diào),所以注釋掉下面的方法
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mBackFlowLayout.setFlowContentLayout(null);
}
自己獨(dú)立封裝,在activity銷毀時(shí)自己調(diào)用釋放。