安卓自定义View

View和ViewGroup

  • 在学习自定义View之前,我们先来了解View和ViewGroup的关系,常说的自定义View其实包括了View和ViewGroup,因为在命名时自定义的View和ViewGroup通常都命名为***View,为了保证文章的准确性避免混淆,本文在阐述时会以字体加粗来区分,即加粗的“自定义View”表示“自定义View或ViewGroup”。
  • 在AndroidAPP中,所有的用户界面元素都是由View和ViewGroup的对象构成的。View是绘制在屏幕上的用户能与之交互的一个对象。而ViewGroup则是一个用于存放其他View(或ViewGroup)对象的布局容器。

用一张图来表示他们的关系:

在这里插入图片描述

  • Android提供了一些View和ViewGroup的可用控件,自定义View就是在这些控件的基础上自定义自己想要的View和ViewGroup。
  • Android的六大基本布局:线性布局LinearLayout、表格布局TableLayout、相对布局RelativeLayout、层布局FrameLayout、绝对布局AbsoluteLayout、网格布局GridLayout,这些都属于ViewGroup。
  • TextView、EditText、Button、ImageView,这些则属于View。

每一个Activity,都有一个ViewGroup(其实只有一个View也行但那就只能有一个了),如MainActivity:

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_main);
    }

以上代码中的this.setContentView(R.layout.activity_main); 为MainActivity设置了一个ViewGroup,这个ViewGroup:R.layout.activity_main是安卓根据布局文件activity_main.xml自动生成的一个ViewGroup。
但通常都需要自定义View自定义View的流程有三步:
测量——onMeasure():决定View的大小
布局——onLayout():决定View在ViewGroup中的位置
绘制——onDraw():如何绘制这个View。
自定义View的方法根据是否重写这些函数分为三种:
1、组合控件
2、继承控件
3、自绘控件

一、组合控件

  • 组合控件就是将系统原有的控件进行组合,构成一个新的控件。这种方式下,不需要开发者自己去重写onMeasure,onLayout,onDraw方法来实现测量、布局以及绘制流程。所以,这种方法事实上只是用现有的View和ViewGroup来组合成一个自定义的ViewGroup,相对简单。
  • 在一个app的各个界面中,标题栏基本上是大同小异,复用率很高。所以经常会将标题栏单独做成一个自定义View,在不同的界面直接引入即可,而不用每次都把标题栏布局一遍。我们就以自定义一个标题栏,包含标题和返回按钮两个控件,来介绍这种组合控件的实现方式。

1、定义标题栏布局文件
定义标题栏的布局文件custom_title_view.xml,因为是标题栏,将它的高度定义为根据内容高度wrap_content。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/holo_red_light">
    <Button
        android:id="@+id/btn_home"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="5dp"
        android:text="Home"
        android:textColor="@android:color/white" />

    <TextView
        android:id="@+id/title_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Title"
        android:textColor="@android:color/white"
        android:textSize="20sp" />
</RelativeLayout>

2、根据布局custom_title_view.xml实现自定义的ViewGroup:CustomTitleView.class

public class CustomTitleView extends FrameLayout implements View.OnClickListener {
    private View.OnClickListener homeButtonOnClickListener;
    private Button homeButton;
    private TextView mTittleView;

    public CustomTitleView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.custom_title_view, this);
        homeButton = findViewById(R.id.btn_home);
        homeButton.setOnClickListener(this);
        mTittleView = findViewById(R.id.title_textview);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_home:
                Toast toast=Toast.makeText(getContext(),"回到主页", Toast.LENGTH_SHORT);
                toast.show();
                break;
        }
    }

    public void setTittle(String title){
        mTittleView.setText(title);
    }
}

3、在Activity的布局文件中添加CustomTitleView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.project.myviewtest.CustomTitleView
        android:id="@+id/customview_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </com.project.myviewtest.CustomTitleView>
</RelativeLayout>

4、在Activity中添加并操作CustomTitleView

public class MainActivity extends AppCompatActivity {

    private CustomTitleView mCustomTitleView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mCustomTitleView = findViewById(R.id.customview_title);
        mCustomTitleView.setTittle("This is Title");
    }
}

二、继承控件

  • 所谓继承控件,其实就是onMesure和onLayout依然原封不动地继承下来,只重写onDraw方法。
    在探究 onDraw方法之前,我们必须先深入了解三个类Canvas(画布),Paint(画笔),Path(路径)。
    如果不了解点这里
  • 与上面所介绍的组合控件不同的是,组合控件只能用来自定义ViewGroup,而继承控件则分别从继承View和继承ViewGroup两个方面来介绍。

1、继承View
示例为在TextView文字下方显示红色下划线
a.定义一个RedUnderlineView.class继承TextView并重写onDraw方法

@SuppressLint("AppCompatCustomView")
public class RedUnderlineView extends TextView {
    public RedUnderlineView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth(5);
        int width = getWidth();
        int height = getBaseline();
        canvas.drawLine(0,height,width,height,paint);
    }
}

b.在xml中调用RedUnderlineView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <com.project.myviewtest.RedUnderlineView
            android:id="@+id/redline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="hellobabyiloveyou"
            android:layout_centerInParent="true">
        </com.project.myviewtest.RedUnderlineView>
        
</RelativeLayout>

2、继承ViewGroup
示例为在layout布局上添加一个红色的半透明蒙板
a.定义一个RedBackGroundLayout.class继承LinearLayout 并重写dispatchDraw方法

  • 关于onDraw和dispatchDraw的关系:
    View是一个相对独立的个体,但ViewGroup不是,ViewGroup里面还包含着其他ViewGroup和View。onDraw绘制的图案只在View或ViewGroup的本体上显示,而dispatchDraw绘制的图案会在ViewGroup内部所有View都显示完成之后再覆盖在上面,所以为了不遮住下面的图案一般都设置成半透明的。
  • 关于设置半透明的两种方法:
    第一种设置透明颜色代码(推荐用)Color.parseColor("#50FF0000")
    第二种alpha设置透明度(不推荐用,坑多)参数值0-255,值越小越透明
public class RedBackGroundLayout extends LinearLayout {
    public RedBackGroundLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.drawColor(Color.parseColor("#50FF0000"));
    }
}

b.在xml中调用RedBackGroundLayout

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

        <com.project.myviewtest.RedBackGroundLayout
            android:id="@+id/redbackground"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_centerInParent="true"
            android:gravity="center">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="这是中心"
                    android:textColor="@android:color/black"
                    android:textSize="50dp"
                    android:layout_centerInParent="true">
                </TextView>
        </com.project.myviewtest.RedBackGroundLayout>

</RelativeLayout>





1、自定义View的属性
2、在View的构造方法中获得我们自定义的属性
3、重写onMesure
4、重写onDraw

自定义View的属性

1、Android attr 是什么

  • attr 的简单理解就是一个属性约束,约束具体属性字段的属性的数据类型(boolean、string、float…)。
  • attr的文件名称不是固定的,只是方便理解和规范,也可以是其他名称,比如arrt、aesa…
  • 其实我们经常在使用,比如我们界面的布局文件,从狭隘的方面理解只要用xml形式文件就涉及到约束,而attr就是其中的一种约束文件而已。

2、Android attr 的创建
首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。
我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型,共有十种:string,color,demension,integer,enum,reference,float,boolean,fraction,flag
format 数据类型参考

<?xml version="1.0" encoding="utf-8"?>
<resources>
 
    <attr name="titleText" format="string" />
    <attr name="titleTextColor" format="color" />
    <attr name="titleTextSize" format="dimension" />
 
    <declare-styleable name="CustomTitleView">
        <attr name="titleText" />
        <attr name="titleTextColor" />
        <attr name="titleTextSize" />
    </declare-styleable>
 
</resources>

3、在布局中声明我们的自定义View

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <!--引用自定义view必须是包名.类名-->
    <com.example.gobangandroid.MyView
        android:layout_width="200dp"
        android:layout_height="100dp"
        custom:titleText="3712"
        custom:titleTextColor="#ff0000"
        custom:titleTextSize="40sp" />

</RelativeLayout>

在View的构造方法中获得我们自定义的属性

public class MyView extends View {
    
    private String mTitleText;//文本
    private int mTitleTextColor;//文本的颜色
    private int mTitleTextSize;//文本的大小
    private Rect mBound;//绘制时控制文本绘制的范围
    private Paint mPaint;

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

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

    /**
     * 获得我自定义的样式属性
     *
     * @param context
     * @param attrs
     * @param defStyle
     */
    public MyView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        /**
         * 获得我们所定义的自定义样式属性
         */
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++)
        {
            int attr = a.getIndex(i);
            switch (attr)
            {
                case R.styleable.CustomTitleView_titleText:
                    mTitleText = a.getString(attr);
                    break;
                case R.styleable.CustomTitleView_titleTextColor:
                    // 默认颜色设置为黑色
                    mTitleTextColor = a.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.CustomTitleView_titleTextSize:
                    // 默认设置为16sp,TypeValue也可以把sp转化为px
                    mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
                            TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                    break;

            }

        }
        a.recycle();

        /**
         * 获得绘制文本的宽和高
         */
        mPaint = new Paint();
        mPaint.setTextSize(mTitleTextSize);
        // mPaint.setColor(mTitleTextColor);
        mBound = new Rect();
        mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);

    }
    
}

重写onDraw,onMesure

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值