美文网首页Android开发经验谈Android技术知识Android开发
自定义ViewGroup_07(小试牛刀) 简单标签布局

自定义ViewGroup_07(小试牛刀) 简单标签布局

作者: __Y_Q | 来源:发表于2020-04-02 00:14 被阅读0次

前面学习了 View 的绘制流程. 今天结合前面学的来自定义一个简单的标签布局.

看效果, 有点丑, 不过大概也算是弄懂了一些这个布局的基本原理. 下面来分析学习一下.

效果图

老规矩还是先分析:

  1. 首先在我们自定义的 ViewGroup 中, 要重写 onMeasure 和 onLayout,
  2. 在 onMeasure 需要针对所有的子 View 进行测量, 在测量子 View 的同时, 还要判断是否需要换行. 如果需要换行, 那么我们自定义 ViewGroup 的高度就需要累加. 我们在测量子 View 的时候, 就把每行的所有 View 保存起来. 还需要把每行的高度存起来. 在 onLayout 布局的时候, 可以直接使用.
  3. 在 onLayout 布局的时候对在 onMeasure 存放的所有行, 进行遍历, 遍历每一行中的每个子 View 对它进行布局. 需要考虑到, 摆放一个子 View 后, 下次摆放子 View 的 left 就需要向后移动.
  4. 无论是在 onMeasure 或者 onLayout 中, 都需要考虑到 margin 和 padding 的情况.

 
一般我们都不会直接继承自 ViewGroup, 在大部分情况下, 都是继承系统已经提供好的, 例如: linearLayout, RelativeLayout 等, 因为他们已经已经写好了一些 onLayout, touch 等等事件. 这里是为了学习, 所以直接继承自了 ViewGroup.

这里就不再分段讲解了, 直接上代码了, 分析的都很清楚.

activity_main 布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <org.zyq.view_day08.LabelLayout
        android:id="@+id/label_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

定义一个简单的Adapter

因本章主要学习的是 ViewGroup 的测量与摆放, 所以就随便弄了一个Adapter.

public abstract class BaseLabelAdapter {
    public abstract int getCount();
    public abstract View getView(int position, ViewGroup parent);
}

自定义 ViewGroup LabelLayout

public class LabelLayout extends ViewGroup {

    private BaseLabelAdapter mAdapter;

    //存放所有行中所有View的集合
    private List<List<View>> mAllLineViews = new ArrayList<>();
    //存放
    private List<Integer> mAllLineHeight = new ArrayList<>();

    public LabelLayout(Context context) {
        this(context, null);
    }

    public LabelLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LabelLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mAllLineViews.clear();
        mAllLineHeight.clear();
        //先拿到用户设置的宽高模式和大小.需要对 warp_content 做处理
        //获取大小
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //获取模式
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
        //判断模式是不是warp_content
        if (modeWidth == MeasureSpec.AT_MOST) {
            throw new RuntimeException("Error: 不允许设置 layout_width 为 wrap_content");
        }

        //获取父容器的宽度和高度, 但是高度需要计算(涉及到子 View 的换行. 换行一次累加一次)
        int parentWidth = widthSize + getPaddingLeft() + getPaddingRight();
        int parentHeight = getPaddingTop() + getPaddingBottom();

        //当前行的宽度和高度
        int currentLineWidth = 0;
        int currentLineHeight = 0;

        //获取子 View 的个数
        int childCount = getChildCount();

        //存放当前行的所有 View
        ArrayList<View> currentLineViews = new ArrayList<>();

        //测量所有的子 View
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);

            //测量子 View, 传入子 View以及父容器的宽高模式.
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);

            //测量完子 View 后, 就可以获取子 View 的宽度和高度 + margin 与 padding.
            ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
            int childWidth = childView.getMeasuredWidth() + childView.getPaddingLeft() + childView.getPaddingRight() + params.leftMargin + params.rightMargin;
            int childHeight = childView.getMeasuredHeight() + childView.getPaddingTop() + childView.getPaddingBottom() + params.topMargin + params.bottomMargin;

            //这里需要考虑换行的问题, 如果一行可以满足, 那么不换行, 父容器的高度不累加, 这里使用一个变量来控制当前行宽.
            //如果当前行宽 + 当前 View 的宽度 > 父容器的宽度, 那么就是需要换行了.也就是父容器的高度累加
            //如果 < 父容器的宽度, 那么当前行宽就累加 当前 View 的宽度.
            if (currentLineWidth + childWidth > parentWidth) {
                //处理换行. 父容器高度累加
                parentHeight += currentLineHeight;

                //需要换行的情况下, 添加到集合
                mAllLineViews.add(currentLineViews);
                mAllLineHeight.add(currentLineHeight);

                //换行后, 需要把当前View添加到下一行. 需要对当前行宽重新赋值
                currentLineWidth = childWidth;
                currentLineHeight = childHeight;
                currentLineViews = new ArrayList<>();
                currentLineViews.add(childView);

            } else {
                currentLineHeight = Math.max(childHeight, currentLineHeight);
                currentLineWidth += childWidth;
                //不换行就把当前 View 添加到当前行的集合中
                currentLineViews.add(childView);
            }
            if (i == childCount - 1) {
                parentHeight += currentLineHeight;
                mAllLineHeight.add(currentLineHeight);
                mAllLineViews.add(currentLineViews);
            }
        }
        // 如果高度是wrap_content或者在ScrollView里面,就设置高度为计算的height值
        setMeasuredDimension(parentWidth, modeHeight == MeasureSpec.AT_MOST || modeHeight == MeasureSpec.UNSPECIFIED ? parentHeight : heightSize);
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //子View的左上右下,四个坐标
        int left = getPaddingLeft();
        int top = getPaddingTop();
        //行数
        int num = mAllLineViews.size();

        for (int i = 0; i < num; i++) {
            //获得第 i 行的所有 View
            List<View> lineViews = mAllLineViews.get(i);
            //获得第 i 行的高度
            int lineHeigh = mAllLineHeight.get(i);
            //遍历摆放第i行的所有View
            for (View view : lineViews) {
                if (view.getVisibility() == View.GONE) {
                    continue;
                }
                ViewGroup.MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
                int childLeft = left + params.leftMargin;
                int childRight = childLeft + view.getMeasuredWidth();
                int childTop = top + params.topMargin;
                int childBottom = childTop + view.getMeasuredHeight();
                view.layout(childLeft, childTop, childRight, childBottom);
                //每摆放一次, left坐标都要向右移动一个 View 的宽度.
                left += view.getMeasuredWidth() + params.leftMargin + params.rightMargin;
            }
            //循环一行之后, 高度累加
            top += lineHeigh;
            //循环一行之后, left 恢复
            left = getPaddingLeft();
        }
    }

    public void setAdapter(BaseLabelAdapter adapter) {
        if (adapter == null) {
            throw new NullPointerException("Error: Adapter 为 null");
        }
        //先清空所有的子 View.
        removeAllViews();

        this.mAdapter = null;
        this.mAdapter = adapter;

        int childCount = this.mAdapter.getCount();

        for (int i = 0; i < childCount; i++) {
            //通过位置获取View
            View childView = this.mAdapter.getView(i, this);
            addView(childView);
        }
    }
}

MainActivity

public class MainActivity extends AppCompatActivity {

    LabelLayout mLayout;
    List<String> mItems;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLayout = findViewById(R.id.label_layout);

        mItems = new ArrayList<>();
        mItems.add("1313131313131313131313131313131313131313131313131313131313131313131313131313131313131313");
        mItems.add("1");
        mItems.add("222222222222222222222");
        mItems.add("333");
        mItems.add("444444444");
        mItems.add("5");
        mItems.add("666666666666666666666666");
        mItems.add("7777777777777");
        mItems.add("8888");
        mItems.add("9999999999");
        mItems.add("1010101010101010");
        mItems.add("111111111111");
        mItems.add("12");
        mItems.add("1313131313131313131313131313131313131313131313131313131313131313131313131313131313131313");
        mItems.add("141414");
        mItems.add("1313131313131313131313131313131313131313131313131313131313131313131313131313131313131313");

        mLayout.setAdapter(new BaseLabelAdapter() {
            @Override
            public int getCount() {
                return mItems.size();
            }

            @Override
            public View getView(int position, ViewGroup parent) {
                View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_label, parent, false);
                TextView tv = view.findViewById(R.id.label_item);
                tv.setText(mItems.get(position));
                return view;
            }
        });
    }
}

R.layout.item_label 布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/label_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:background="@drawable/item_label_bg"
        android:padding="5dp"
        android:text="123" />
</LinearLayout>

 
 


大概就是这样, 大家最好还是先学习 View 的绘制流程 后, 再来分析这个, 相信会更加得心应手.

也会加深对 View 绘制流程中的 onMeasure 和 onLayout 的理解.

今天就到了这里了, 晚安, 小牛们.

明天继续努力 !!!

相关文章

网友评论

    本文标题:自定义ViewGroup_07(小试牛刀) 简单标签布局

    本文链接:https://www.haomeiwen.com/subject/cdizuhtx.html