自定义View系列(四)

上一篇,我们做了一个带有背景色的TextView,属于自定义控件,这一片我们看一下,如何进行自定义布局。首先,我们事先一个简单的布局,以常见的标题栏为列,如下图:

                   

一个返回键,一个标题,还有右边按钮。由图可知,该标题栏中有三个控件,有多个控件,就会涉及到排布的问题,那么用之前的继承View,重写onMeasure和onDraw就不能解决问题了,这时,我们要继承ViewGroup,重写onLayout方法。也就是今天的主要内筒,自定义布局。

1,先看主函数:

public class ThreadActivity extends AppCompatActivity{

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.thread_activity);
    }
}

什么也没写;

2.MyViewGroup:

public class MyViewGroup extends ViewGroup{

    private int MargnLeft = 20;
    private int MargnRight = 20;
    private int MargnTop = 20;
    public MyViewGroup(Context context) {
        this(context,null);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //遍历子View,测量每个View的大小
        for(int i = 0; i < getChildCount(); i++){
            View view = getChildAt(i);
            measureChild(view,widthMeasureSpec,heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        for(int j = 0; j < getChildCount(); j++){
            View view = getChildAt(j);
            if(j == 0){//返回键的位置
                view.layout(MargnLeft,MargnTop,MargnLeft + view.getMeasuredWidth(),
                        MargnTop +view.getMeasuredHeight());
            }else if(j == 1){//标题居中
                view.layout(getWidth()/2 - view.getMeasuredWidth() / 2,MargnTop,
                        getWidth()/2 + view.getMeasuredWidth() / 2,MargnTop + view.getMeasuredHeight());
            }else if(j == 2){//右边按钮位置
                view.layout(getWidth() - MargnRight - view.getMeasuredWidth(),MargnTop,getWidth() -MargnRight,
                         MargnTop +view.getMeasuredHeight());
            }
        }
    }
}

可以看到MyViewGroup继承了ViewGroup,很明显,这是一个自定义的布局,紧接着重写了onMeasure()和onlayout()这两个方法,在onMeasure中通过一个for循环,对ViewGroup中的子控件进行遍历,然后通过

View view = getChildAt(i);
measureChild(view,widthMeasureSpec,heightMeasureSpec);

可以得到子控件View的尺寸。

然后主要看onLayout()方法,在这里对各个子控件进行了排布,以确定,控件在画布上的具体位置。关于onLayout()的介绍我们在第一节自定义View系列(一)中,已经介绍过了。关于源码这里不再贴出,这里着重说一下里面参数的含义:第一个是判断子控件view的位置是否发生变化。剩下的四个,分别表示子控件相对于父控件ViewGroup的左、上、右、下边界的距离。如下图:

              (图片来源:[GcsSloop]Github上面的)


看到上面的图片,onlayout中各个View的位置的设置就不难看懂了。位置设置好之后基本就完成了,我们就可以像里面放控件了:

3.xml:

<com.example.administrator.myview1.MyViewGroup
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:src="@mipmap/icon_black_back"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="标题标题标题标题"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="合同"/>

,运行一下看效果,如下图:

可以看到效果已经实现了。当然具体项目中的各种设置,这里没有添加,主要是为了看一下效果。网上很多大神都通关自定义ViewGroup实现一个流式布局,像搜索记录类似的。这里呢,小编也参考一位道友的博客撸出了下面的一段代码,有兴趣的可以看看:

1.MyViewGroup2:

public class MyViewGroup2 extends ViewGroup{

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

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean bo, int l, int t, int r, int b) {
        //获得子控件的数量
        int childCount = getChildCount();
        //当前子控件的左边坐标
        int cLeft = 0;
        //当前子控件的上边坐标
        int cTop = 0;
        //ViewGroup整体宽度
        int width = getWidth();
        //行高
        int lineHeight = 0;
        //遍历所有子控件
        for(int i = 0; i < childCount; i++){
            //获取当前控件
            View childAt = getChildAt(i);
            //获取宽度
            int cWidth = childAt.getMeasuredWidth();
            //获取高度
            int cHeight = childAt.getMeasuredHeight();
            //当前控件右边
            int cRight = cLeft + cWidth;
            //当前控件下边
            int cBottom = cTop + cHeight;
            //判断是否换行
            if(cRight > width){
                //如果换行重新计算上下左右地值
                cLeft = 0;
                cRight = cLeft + cWidth;
                cTop += lineHeight;
                cBottom = cTop + cHeight;
                //换行后,取第一个控件高度作为最大行高
                lineHeight = cHeight;
            }else{
                //如果不换行,需要计算最大高度,0件最大高度最为行高
                lineHeight = Math.max(lineHeight,cHeight);
            }
            childAt.layout(cLeft,cTop,cRight,cBottom);
            //横向向后移动一个,前面控件的右边作为后面控件的左边
            cLeft = cRight;
        }
    }
}

2.xml:


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.administrator.myview1.MyViewGroup2
        android:id="@+id/myviewgroup"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="10dp"/>

</LinearLayout>

button_layout:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>


3.主函数:

public class FourActivity extends AppCompatActivity{

    private MyViewGroup2 mGroup2;
    private String [] team = {"飞狐外传","雪山飞狐","连城诀","天龙八部","射雕英雄换","白马啸北风","鹿鼎记",
            "笑傲江湖笑傲江湖","书剑恩仇录","神雕侠侣","侠客行","倚天屠龙记","碧血剑","鸳鸯刀鸳鸯刀"};
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.four_activity);
        init();
    }

    private void init() {
        mGroup2 = findViewById(R.id.myviewgroup);
        mGroup2.removeAllViews();
        for(int i = 0; i < 14; i++){
            View view = LayoutInflater.from(this).inflate(R.layout.button_layout,null);
            final Button button = view.findViewById(R.id.btn);
            if(i == 6 )
                button.setPadding(0,100,0,100);
            button.setText(team[i]);
            mGroup2.addView(view,i);

            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(FourActivity.this,button.getText().toString(),Toast.LENGTH_LONG).show();
                }
            });
        }
    }

效果如下:

参考:http://www.jb51.net/article/115568.htm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值