這是我的第一篇文章,想了很久不知道寫什么內(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?,可以去看看,對比一下。
有哪里不懂的可以提問,有哪里不對的可以指正,謝謝。