自定义控件

自定义控件

整理自:参考:https://www.jianshu.com/p/c84693096e41
《第一行代码》

View是安卓中最基本的UI组件,可以在屏幕上绘制一个矩形区域,并相应这个区域各种事件。ViewGroup是特殊的View,它包含很多子View和子ViewGroup,是一个用来放置布局和控件的容器。

当系统提供的控件不足以满足我们的业务需求的时候,就要自定义控件了。

一.例子:自制一个自定义标题栏控件

(1)首先定义一个标题栏布局

我先来一个丑丑的,名字叫title

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:background="@color/colorTitle"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/back"
        android:text="back"
        android:layout_margin="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/title"
        android:text="title"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:gravity="center"
        android:textSize="24dp"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/edit"
        android:text="edit"
        android:layout_margin="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

如果想要复用布局,不需要逻辑可以直接用这个语句引入布局,需要逻辑处理才需要自定义控件,这个时候直接引入,和普通控件用法差不多,但是要完整包名

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    
   <com.example.uicustomviewtitle.TitleLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content">
   </com.example.uicustomviewtitle.TitleLayout>

</LinearLayout>

(2)然后让这个布局继承自已有的一些布局,这里我继承LinearLayout,重写有LinearLayout两个参数的的构造方法,在引用这个自定义标题栏的时候就会调用构造函数。

接着用LayoutInflater动态加载布局,用其from()方法可以构造出LayoutInflater对象,然后调用inflate()方法实现动态加载,其接受两个参数,第一个是要加载的布局,第二个是给加载好的布局传入一个父布局,这里我们指定父布局TitleLayout,所以传入this

最后为按钮增加逻辑。

public class TitleLayout extends LinearLayout {
    public TitleLayout(Context context, AttributeSet attrs){
        super(context,attrs);
        LayoutInflater.from(context).inflate(R.layout.title,this);
        //为按钮添加逻辑
        Button backButton=(Button)findViewById(R.id.back);
        backButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ((Activity)getContext()).finish();
            }
        });
        Button editButton=(Button)findViewById(R.id.edit);
        editButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getContext(),"hhah",Toast.LENGTH_SHORT).show();
            }
        });
    }
}

以上就是自定义控件了
二.继续分析自定义View

通常来讲,自定义View需要重写两个方法,一个是onMeasure(),另一个是onDraw(),

1.onMeasure()方法

(1)用于测量屏幕大小,当自带的(wrap_content以及match_parent)满足不了需求时,就需要重写这个了。用于测量屏幕的大小

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

两个参数widthMeasureSpec, heightMeasureSpec,包含控件的宽高信息,此外还有测量模式信息,测量模式有以下三种

测量模式表示意思
UNSPECIFIED父容器没有对当前View有任何限制,当前View可以任意取尺寸
EXACTLY当前的尺寸就是当前View应该取的尺寸
AT_MOST当前尺寸是当前View能取的最大尺寸

获得测量模式以及宽高用以下函数

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);

因为int有32位,三种测量模式用2位可以表示,余下的30位就是用来表示宽高的。

对应于wrap_content以及match_parent以及设置固定尺寸对应如下

match_parent—>EXACTLY。怎么理解呢?match_parent就是要利用父View给我们提供的所有剩余空间,而父View剩余空间是确定的,也就是这个测量模式的整数里面存放的尺寸。

wrap_content—>AT_MOST。怎么理解:就是我们想要将大小设置为包裹我们的view内容,那么尺寸大小就是父View给我们作为参考的尺寸,只要不超过这个尺寸就可以啦,具体尺寸就根据我们的需求去设定。

固定尺寸(如100dp)—>EXACTLY。用户自己指定了尺寸大小,我们就不用再去干涉了,当然是以指定的大小为主啦。

(2)重写onMeasure方法

这里我们实现一个正方形控件,默认边长100dp

首先自定义一个MyView,继承自View,然后实现至少下面两个构造方法,再重写onMeasure(),最后在布局文件中引入

package com.example.uicustomviewtitle;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

public class MyView extends View {
    MyView(Context context){
        super(context);
    }
    MyView(Context context, AttributeSet attrs){
        super(context,attrs);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //这里实现绘制一个正方形控件的功能
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width=getSize(100,widthMeasureSpec);
        int height=getSize(100,heightMeasureSpec);
        //不确定hight,width那个小
        int a=height>width?width:height;
        setMeasuredDimension(a,a);

    }
    private int getSize(int defaultSize,int measureSize){
        int mySize=defaultSize;
        int size=MeasureSpec.getSize(measureSize);
        int mode=MeasureSpec.getMode(measureSize);
        switch(mode){
            case MeasureSpec.UNSPECIFIED://没有指定大小,设置为默认大小
                mySize=defaultSize;
                break;
            case MeasureSpec.AT_MOST://指定的是最大值,可以设置为指定的值,也可以设置成为别的
                mySize=size;
                break;
            case MeasureSpec.EXACTLY://指定了固定值,就设置为测量的值
                mySize=size;
                break;
            default: break;
        }
        return mySize;
    }
}

布局

<com.example.uicustomviewtitle.MyView
 android:background="@color/colorAccent"
 android:layout_width="100dp"
 android:layout_height="match_parent" />
2.onDraw()方法

onMeasure()方法让我们可以确定控件的尺寸,具体样子可以通过onDraw()方法绘制出来,直接在画板Canvas对象上绘制即可

这里我画一个圆

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);//继承父类的onDraw()方法,里面实现了绘制背景颜色等方法
        int r=getMeasuredWidth()/2;//这里也可以是getMeasuredWidth/2,因为前面宽高已经相等了。圆半径
        //定义圆心坐标
        int scaleX=getLeft()+r;//横坐标当前View控件距离左部距离加半径
        int scaleY=getTop()+r;//纵坐标当前View距离顶部距离加半径
        Paint paint=new Paint();
        paint.setColor(Color.GREEN);
        //开始绘制
        canvas.drawCircle(scaleX,scaleY,r,paint);
    }

最后就是这样了
在这里插入图片描述

三.自定义ViewGroup

ViewGroup不仅有本身,还有里面包含的子view,所以处理起来比自定义view复杂一些

(1)首先测量所有子view的大小,接下来根据子view和ViewGroup的逻辑给出ViewGroup的大小

重写onMeasure()方法实现这一点

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);//可以触发所有子View的onMeasure,区分measureChild,只有一个
        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int widthSize=MeasureSpec.getSize(widthMeasureSpec);
        int heightSize=MeasureSpec.getSize(heightMeasureSpec);
        int heightMode=MeasureSpec.getMode(heightMeasureSpec);
        int childCount=getChildCount();
        if(childCount==0){//没有子控件,不占据位置
            setMeasuredDimension(0,0);
        }
        else{//如果宽高都装填内容
            if(widthMode==MeasureSpec.AT_MOST&&heightMode==MeasureSpec.AT_MOST){
                //我们将高度设置为所有子View的高度相加,宽度设为子View中最大的宽度
                int width=getMaxWidth();
                int height=getTotalHeight();
                setMeasuredDimension(width,height);
            }else if(widthMode==MeasureSpec.AT_MOST){//只有宽度包裹内容
                setMeasuredDimension(getMaxWidth(),heightSize);
            }else if(heightMode==MeasureSpec.AT_MOST){//只有高度包裹内容
                setMeasuredDimension(widthSize,getTotalHeight());
            }
        }

    }
   public int getMaxWidth(){
        int maxWidth=0;
        for(int i=0;i<getChildCount();i++){
            View childView=getChildAt(i);
            if(childView.getMeasuredWidth()>maxWidth){
                maxWidth=childView.getMeasuredWidth();
            }
        }
        return maxWidth;
   }
    public int getTotalHeight(){
        int totalHeight=0;
        for(int i=0;i<getChildCount();i++){
            totalHeight+=getChildAt(i).getMeasuredHeight();
        }
        return totalHeight;
    }
}

(2)然后就是每个view怎么摆放的问题,水平垂直,给他们分配出空间进行摆放,重写onLayout()方法

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount=getChildCount();
        int curHeight=t;
        for(int i=0;i<childCount;i++){
            View child=getChildAt(i);
            int height=child.getMeasuredHeight();
            int width=child.getMeasuredWidth();
            //控件的左上右下布局
            child.layout(l,curHeight,l+width,curHeight+height);
            curHeight+=height;
        }
    }

(3)定义一下布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

   <com.example.customviewgroup.MyViewGroup
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:background="#ff9900">
       <Button
           android:layout_width="100dp"
           android:layout_height="wrap_content"
           android:text="btn" />

       <Button
           android:layout_width="200dp"
           android:layout_height="wrap_content"
           android:text="btn" />

       <Button
           android:layout_width="50dp"
           android:layout_height="wrap_content"
           android:text="btn" />
   </com.example.customviewgroup.MyViewGroup>
</LinearLayout>

最后效果
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值