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);
}
}