Android中的超简单自定义流式布局

 

一.继承ViewGroup来确定流式布局

    
    /**
    * 流式布局的基本步骤,继承ViewGroup
    * 如果想要完全自定义布局内控件的排列方式,就直接继承与ViewGroup
    * 继承ViewGroup,必须要重写onLayout
    */
    public class FlowView extends ViewGroup{

    public FlowView(Context context) {
        super(context);
    }

    public FlowView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    //生成默认的布局参数的方法
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(),attrs);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);


        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);


        //只有定义成wrap_content才需要计算尺寸
        //声明变量记录流式布局最后的宽高
        int width = 0;
        int height = 0;
        //记录每一行的宽度和高度的值
        int lineHeight = 0;
        int lineWidth = 0;


        //获取当前布局当中子view的格式
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);    //获取指定角标的子view
            //计算子view的宽度和高度
            measureChild(child,widthMeasureSpec,heightMeasureSpec);
            //得到子view的外边框的参数对象
            MarginLayoutParams mp = (MarginLayoutParams) child.getLayoutParams();
            //获取子view所占据的宽度和高度
            int childHeight = child.getMeasuredHeight()+mp.topMargin+mp.bottomMargin;
            int childWidht = child.getMeasuredWidth()+mp.leftMargin+mp.rightMargin;


            /**判断当前遍历到的view需不需要换行,如果换行就能要行数+1
             * 换行的依据
             * 如果当前已经放入的内容的宽度+即将放入的view的宽度>已知的行的宽度-内边距
             * 就需要换行了。
             * */
            if (lineWidth+childWidht>sizeWidth-getPaddingRight()-getPaddingLeft()){
                //换行
                //1.得到当前几行当中最大的宽度,存储起来,作为流失布局的宽度
                width = Math.max(width,lineWidth);
                //2.新开启一行,行宽发生变化
                lineWidth = childWidht;
                //3.叠加新的一行的高度
                height+=lineHeight;
                //4.新开启一行之后,新行的高度就是新放入的view的高度
                lineHeight = childHeight;
            }else {
                //不用换行
                lineWidth+=childWidht;   //当前行宽度增加了
                lineHeight = Math.max(lineHeight,childHeight);
            }
            //分析发现,最后一行没有添加到高度中,也没有计算最后一行的宽度和目前宽度的大小
            //保证最后一行只加一次
            if (i==count-1){    //判断是为了确保只加一次
                height+=lineHeight;
                width = Math.max(width,lineWidth);
            }
        }
        //只有设置成wrap_content时需要计算,设置成精确值时,给多大就多大
        setMeasuredDimension((modeWidth==MeasureSpec.EXACTLY)?sizeWidth:width+getPaddingLeft()+getPaddingRight(),
                (modeHeight==MeasureSpec.EXACTLY)?sizeHeight:height+getPaddingTop()+getPaddingBottom());
    }
    /**
     * onLayout :在此方法中去排列子控件
     * changed :
     * */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //已每一行作为单位把子view放置到容器当中
        List<List<View>>mAllViews = new ArrayList<List<View>>();
        //记录每一行的高度
        List<Integer>mLineHeight = new ArrayList<Integer>();


        //获取当前布局的宽度
        int width = getWidth();
        //记录每一行的宽度和高度
        int lineHeight = 0;
        int lineWidth = 0;


        //记录每一行的view
        List<View>mLineViews = new ArrayList<>();


        //获取子view的个数
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            //拿到每一个子view对象
            View child = getChildAt(i);
            //获取子view外边框的参数
            MarginLayoutParams mp = (MarginLayoutParams) child.getLayoutParams();
            //能够获取子控件存在的宽,高
            int childHeight = child.getMeasuredHeight()+mp.topMargin+mp.bottomMargin;
            int childWidth = child.getMeasuredWidth()+mp.leftMargin+mp.rightMargin;


            if (childWidth+lineWidth>width-getPaddingRight()-getPaddingLeft()){
                //做换行的工作
                mLineHeight.add(lineHeight);
                //记录当前行的高度,直到了当前行的高度,就能直到放入此行的view距离top有多大
                //添加这一行的view到总容器当中
                mAllViews.add(mLineViews);
                //新开启一行
                lineWidth = 0;
                lineHeight = 0;
                //更新记录,创建一个新的容器对象,存放换行之后的view的对象
                mLineViews = new ArrayList<>();
            }
            //不换行做的操作和换行之后所做的操作是一致的
            //叠加view的宽度到原来行中
            lineWidth+=childWidth;
            //和之前此行中的其他view比较,取出最高的一个,作为行的高度
            lineHeight = Math.max(lineHeight,childHeight);
            //把当前的view添加到存储此行的容器当中
            mLineViews.add(child);
        }
        /**
         * 分析发现:  最后一行的高度没有添加到高度集合中
         * 最后一行存放view的容器也没有添加到总容器当中
         * */
        mLineHeight.add(lineHeight);
        mAllViews.add(mLineViews);
        //刚才的工作,是为了给容器当中的控件,按照行的方式进行分组
        //LIST<LIST>,其中的泛型,就是分组的结果,    list<list>的长度代表行数


        //获取父布局的padding
        int left = getPaddingLeft();
        int top = getPaddingTop();


        //按照行的方式遍历view
        for (int i = 0; i < mAllViews.size(); i++) {
            mLineViews = mAllViews.get(i);     //拿到存放了每一行view的容器
            //拿到每一行的高度
            lineHeight = mLineHeight.get(i);
            //遍历每一行当中的view进行排列
            for (int j = 0; j < mLineViews.size(); j++) {
                //拿到这一行当中的所有的view
                View child = mLineViews.get(j);
                MarginLayoutParams mp = (MarginLayoutParams) child.getLayoutParams();
                //知道view的宽度,边距,此行的高度,就可以了解到view放置的左上右下
                int lf = left+mp.leftMargin;
                int tp = top+mp.topMargin;
                int rg = lf+child.getMeasuredWidth();
                int bm = tp+child.getMeasuredHeight();


                //向布局当中添加view
                child.layout(lf,tp,rg,bm);
                left+=child.getMeasuredWidth()+mp.leftMargin+mp.rightMargin;
            }
            //换行
            left = getPaddingLeft();
            top+=lineHeight;
        }
    }
    }
二.在layout文件里面创建flow_item.xml文件里面写TextView的基本的view


    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
    
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="20sp"
    android:textColor="@color/colorPrimary"
    android:background="@drawable/item_bg"
    android:layout_margin="5dp">
    
    </TextView>
三.在Activtity布局文件中中添加自定义控件


    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_custom09"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.qianfeng.customviewrtest.demo09.CustomActivity09">
    <com.qianfeng.customviewrtest.demo09.FlowView
        android:id="@+id/flow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#c0c0c0">


    </com.qianfeng.customviewrtest.demo09.FlowView>
    </RelativeLayout>


四.在Activity中实例化自定义控件,将添加了文字的textView用mFlowView.addView(tv)方法写入流式图中


    public class CustomActivity09 extends AppCompatActivity {
    private FlowView mFlowView;
    private String[]items = {"飞狐外传","雪山飞狐","连城决","天龙八部","射雕英雄传","白马啸西风","鹿鼎记","笑傲江湖","书剑恩仇录","神雕侠侣",
            "倚天屠龙记","碧血剑","鸳鸯刀","疯狂JAVA讲义","第一行代码","安卓艺术鉴赏","安卓群英传",
            "三国\n演义","水浒传","红楼梦","西游记"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom09);
        mFlowView = (FlowView) findViewById(R.id.flow);


        initView();
    }


    private void initView(){
        for (int i = 0; i < items.length; i++) {
            TextView tv = (TextView) LayoutInflater.from(this).inflate(R.layout.flow_item, mFlowView, false);
            tv.setText(items[i]);
            mFlowView.addView(tv);
        }
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值