自適應(yīng)布局FlowLayout

這是我的第一篇文章,想了很久不知道寫什么內(nèi)容,估計目前也沒有什么能力寫深奧的,那就寫寫之前寫過的自定義view,分享一下,有不正確的地方往指正,大家共同學(xué)習(xí)。

好了,正文來了,這篇是主要寫自適應(yīng)布局,也就是添加的view從左到右排好,若新一個view在這一行放不下就放在下一行。

自定義view第一步是在attr.xml寫屬性,不過FlowLayout比較簡單沒有自定義屬性,直接跳到后面的測量,布局等,那么就先重寫onMeasure()方法

@Override

? ? protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

? ? ? ? int measureWidth = MeasureSpec.getSize(widthMeasureSpec);

? ? ? ? int measureHeight = MeasureSpec.getSize(heightMeasureSpec);

? ? ? ? int widthMode = MeasureSpec.getMode(widthMeasureSpec);

? ? ? ? int heightMode = MeasureSpec.getMode(heightMeasureSpec);

? ? ? ? //每行的的寬度

? ? ? ? int width = getPaddingStart() + getPaddingEnd();

? ? ? ? //自適應(yīng)的長度

? ? ? ? int height = getPaddingTop() + getPaddingBottom();

? ? ? ? //最大寬度

? ? ? ? int maxWidth = 0;

? ? ? ? //每一行的最大長度

? ? ? ? int maxHeight = 0;

? ? ? ? //遍歷子view

? ? ? ? for (int i = 0; i < getChildCount(); i++) {

? ? ? ? ? ? View view = getChildAt(i);

? ? ? ? ? ? //測量子view

? ? ? ? ? ? measureChild(view, widthMeasureSpec, heightMeasureSpec);

? ? ? ? ? ? //獲取子view的外邊距

? ? ? ? ? ? MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();

? ? ? ? ? ? //子view的寬,要加上子view的外邊距,不然margin屬性設(shè)置了沒效果

? ? ? ? ? ? int w = view.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;

? ? ? ? ? ? //子view的高

? ? ? ? ? ? int h = view.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;

? ? ? ? ? ? //判斷是否是該行最大長度

? ? ? ? ? ? maxHeight = maxHeight > h ? maxHeight : h;

? ? ? ? ? ? //判斷加入該view后是否超過測量寬度,超過的話就換行

? ? ? ? ? ? if (measureWidth < w + width) {

? ? ? ? ? ? //判斷該view是否超過父容器的最大值,是的話,設(shè)該view的寬度為父容器的測量值

? ? ? ? ? ? ? ? if (w > measureWidth) {

? ? ? ? ? ? ? ? ? ? w = measureWidth;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? //換行,新一行的寬度重置,最大長度增加

? ? ? ? ? ? ? ? width = getPaddingStart() + getPaddingEnd();

? ? ? ? ? ? ? ? height += maxHeight;

? ? ? ? ? ? }

? ? ? ? ? ? //該行加入子view寬度

? ? ? ? ? ? width += w;

? ? ? ? ? ? //獲取最大寬度,其實最大也就是父容器的測量值

? ? ? ? ? ? maxWidth = maxWidth > width ? maxWidth : width;

? ? ? ? ? ? //因為是從0開始的,所以最后一個view時需要加上當前這一行的高度

? ? ? ? ? ? if (i == getChildCount() - 1) {

? ? ? ? ? ? ? ? height += maxHeight;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {

? ? ? ? ? ? setMeasuredDimension(measureWidth, measureHeight);

? ? ? ? } else if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.AT_MOST) {

? ? ? ? ? ? setMeasuredDimension(measureWidth, height);

? ? ? ? } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.EXACTLY) {

? ? ? ? ? ? setMeasuredDimension(maxWidth, measureHeight);

? ? ? ? } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {

? ? ? ? ? ? setMeasuredDimension(maxWidth, height);

? ? ? ? } else {

? ? ? ? ? ? setMeasuredDimension(measureWidth, height);

? ? ? ? }

? ? }

以上就是重寫后的onMeasure()方法,看注釋應(yīng)該就懂了,不過有一點需要注意,就是MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();這段,獲取子view的外邊距,這就要重寫generateLayoutParams()和generateDefaultLayoutParams()這兩個方法了

@Override

? ? protected LayoutParams generateLayoutParams(LayoutParams p) {

? ? ? ? return new MarginLayoutParams(p);

? ? }

? ? @Override

? ? public LayoutParams generateLayoutParams(AttributeSet attrs) {

? ? ? ? return new MarginLayoutParams(getContext(), attrs);

? ? }

? ? @Override

? ? protected LayoutParams generateDefaultLayoutParams() {

? ? ? ? return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

? ? }

因為MarginLayoutParams時繼承LayoutParams的,而且有外邊距屬性,所以將LayoutParams轉(zhuǎn)化為MarginLayoutParams就好了,這就可以拿到子view的外邊距屬性了。

那么測量完之后呢,那就是布局了,布局就時重寫onLayout()方法

@Override

? ? protected void onLayout(boolean changed, int l, int t, int r, int b) {

? ? //該行的開始寬度,是要加上父容器的內(nèi)邊距

? ? ? ? int width = getPaddingStart();

? ? ? ? //這是總高度

? ? ? ? int height = getPaddingTop();

? ? ? ? //這是目標行的最大高度

? ? ? ? int maxHeight = 0;

? ? ? ? //目標行的最大寬度

? ? ? ? int maxWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd();

? ? ? ? //遍歷子view

? ? ? ? for (int i = 0; i < getChildCount(); i++) {

? ? ? ? ? ? View view = getChildAt(i);

? ? ? ? ? ? MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();

? ? ? ? ? ? //子view寬度

? ? ? ? ? ? int w = view.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;

? ? ? ? ? ? //子view高度

? ? ? ? ? ? int h = view.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;

? ? ? ? ? ? maxHeight = maxHeight > h ? maxHeight : h;

? ? ? ? ? ? if (maxWidth < w + width) {

? ? ? ? ? ? ? ? if (w > maxWidth) {

? ? ? ? ? ? ? ? ? ? w = maxWidth;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? //換行,重置該行的開始寬度,目標行的高度增加

? ? ? ? ? ? ? ? width = getPaddingStart();

? ? ? ? ? ? ? ? height += maxHeight;

? ? ? ? ? ? ? ? maxHeight = 0;

? ? ? ? ? ? }

? ? ? ? ? ? width += w;

? ? ? ? ? ? //子view的左上角的坐標,width已經(jīng)加上子view的寬度了,只要減去w就是開始坐標,加上子view的左外邊距就可以了

? ? ? ? ? ? int viewL = width - w + layoutParams.leftMargin;

? ? ? ? ? ? int viewR = width - layoutParams.rightMargin;

? ? ? ? ? ? int viewT = height + layoutParams.topMargin;

? ? ? ? ? ? int viewB = ((height + h) > getMeasuredHeight() ? getMeasuredHeight() : (h + height)) - layoutParams.bottomMargin;

? ? ? ? ? ? view.layout(viewL, viewT, viewR, viewB);

? ? ? ? }

? ? }

以上就是onLayout()方法,其實跟onMeasure()方法差不多,那么我們來看看效果吧,首先添加到activity的布局里

<android.support.constraint.ConstraintLayout

? ? ? ? android:layout_width="match_parent"

? ? ? ? android:layout_height="match_parent"

? ? ? ? tools:context="com.project.viewtest.activity.FlowActivity">

? ? ? ? <Button

? ? ? ? ? ? android:layout_width="wrap_content"

? ? ? ? ? ? android:layout_height="wrap_content"

? ? ? ? ? ? android:text="add"

? ? ? ? ? ? android:id="@+id/flow_add"/>

? ? ? ? <Button

? ? ? ? ? ? android:layout_width="wrap_content"

? ? ? ? ? ? android:layout_height="wrap_content"

? ? ? ? ? ? android:text="back"

? ? ? ? ? ? android:id="@+id/flow_back"

? ? ? ? ? ? app:layout_constraintLeft_toRightOf="@id/flow_add"/>

? ? ? ? <com.project.viewtest.widget.FlowLayout

? ? ? ? ? ? android:layout_width="match_parent"

? ? ? ? ? ? android:layout_height="wrap_content"

? ? ? ? ? ? android:id="@+id/flow_layout"

? ? ? ? ? ? app:layout_constraintTop_toBottomOf="@id/flow_add"/>

? ? </android.support.constraint.ConstraintLayout>

從布局可以看出,有一個添加按鈕,就是給FlowLayout添加子view的,那么看一下activity的內(nèi)容

public class FlowActivity extends AppCompatActivity {

? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? setContentView(R.layout.activity_flow);

? ? ? ? final FlowLayout layout = findViewById(R.id.flow_layout);

? ? ? ? //返回按鈕

? ? ? ? findViewById(R.id.flow_back).setOnClickListener(new View.OnClickListener() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onClick(View v) {

? ? ? ? ? ? ? ? finish();

? ? ? ? ? ? }

? ? ? ? });

? ? ? ? //添加按鈕

? ? ? ? findViewById(R.id.flow_add).setOnClickListener(new View.OnClickListener() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onClick(View v) {

? ? ? ? ? ? ? ? TextView textView = new TextView(FlowActivity.this);

? ? ? ? ? ? ? ? textView.setText(getText());

? ? ? ? ? ? ? ? textView.setBackgroundResource(R.drawable.text_bg);

? ? ? ? ? ? ? ? ViewGroup.MarginLayoutParams layoutParams = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

? ? ? ? ? ? ? ? //設(shè)置外邊距

? ? ? ? ? ? ? ? layoutParams.setMargins(15, 15, 0, 0);

? ? ? ? ? ? ? ? textView.setLayoutParams(layoutParams);

? ? ? ? ? ? ? ? //添加

? ? ? ? ? ? ? ? layout.addView(textView);

? ? ? ? ? ? }

? ? ? ? });

? ? }

//獲取隨機字符串

? ? private String getText() {

? ? ? ? int count = (int) ((Math.random() + Math.random()) * 10);

? ? ? ? StringBuilder builder = new StringBuilder();

? ? ? ? for (int i = 0; i < count; i++) {

? ? ? ? ? ? builder.append((char) ((int) (Math.random() * 93) + 32));

? ? ? ? }

? ? ? ? Log.i("flowActivity", "getText: " + count + "/" + builder.toString());

? ? ? ? return builder.toString();

? ? }

}

現(xiàn)在可以看效果了

添加view后的

這樣就寫好一個自適應(yīng)FlowLayout了,這是我在啟艦大神的博客看到的,不過我沒看代碼,就是想自己寫一個,附上啟艦大神的博客:https://blog.csdn.net/harvic880925?t=1?,可以去看看,對比一下。

有哪里不懂的可以提問,有哪里不對的可以指正,謝謝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容