如果说要按类型来划分的话,自定义View的实现方式大概可以分为三种,组合控件、继承控件、以及自绘控件。那么下面我们就来依次学习一下,每种方式分别是如何自定义View的。
一、组合控件
组合控件的意思就是,我们并不需要自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但我们可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。
举个例子来说,内容分类条就是个很常见的组合控件(如下图),很多内容的顶部都会放置一个内容分类条,上面有内容对应的分类和获取更多的按钮,点击更多后就可以进入获取更多的页面。那么下面我们就来尝试去实现它。
新建一个titlebar.xml,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#f8f8f8"
android:orientation="horizontal" >
<TextView
android:id="@+id/layout_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="10dip"
android:text="专辑"
android:textSize="18sp" />
<TextView
android:id="@+id/getMore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="20dip"
android:drawablePadding="10dp"
android:drawableRight="@drawable/btn_manage_more"
android:text="更多" />
</RelativeLayout>
在这个布局里,TextView(title)就是内容分类的标题,TextView(getMore)就是获取更多的“按钮”。
接下来创建TitleView继承RelativeLayout,代码如下:
public class TitleView extends RelativeLayout
{
private TextView tvTitle;
private TextView tvMore;
public TitleView(Context context, AttributeSet attrs)
{
super(context, attrs);
// LayoutInflater.from(context).inflate(R.layout.titlebar, this, true);
LayoutInflater.from(context).inflate(R.layout.titlebar, this);
tvTitle = (TextView) findViewById(R.id.layout_title);
tvMore = (TextView) findViewById(R.id.getMore);
}
public void setTitle(String title)
{
tvTitle.setText(title);
}
public void setGetMoreListener(OnClickListener l)
{
tvMore.setOnClickListener(l);
}
}
在类TitleView中,通过LayoutInflater来加载布局。
先来看一下LayoutInflater的基本用法吧,它的用法非常简单,首先需要获取到LayoutInflater的实例,有两种方法可以获取到,第一种写法如下:
- LayoutInflater layoutInflater = LayoutInflater.from(context);
- LayoutInflater layoutInflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- layoutInflater.inflate(resourceId, root);
其实,inflate()方法还有个接收三个参数的方法重载,结构如下:
- inflate(int resource, ViewGroup root, boolean attachToRoot)
1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。
2. 如果root不为null,attachToRoot设为true,则会在加载的布局文件的最外层再嵌套一层root布局。
3. 如果root不为null,attachToRoot设为false,则root参数失去作用。
4. 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。
到了这里,一个自定义的内容分类条就完成了。要想使用,直接在布局文件中添加如下代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.demo.TitleView
android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_height="60dp" >
</com.example.demo.TitleView>
</LinearLayout>
这样就成功将控件引入到布局文件中了,运行一下程序,效果如下图所示:
二、继承控件
继承控件的意思就是,顾名思义我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件。除此之外,我们还可以在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能。
在上面讲到的组合控件,TitleView继承了RelativeLayout,就是一个简单的继承控件。只是没有添加新的功能。增添新功能的继承控件,我们在后面再讲这个问题……
三、自绘控件
自绘控件的意思就是,这个View上所展现的内容全部都是我们自己绘制出来的。绘制的代码是写在onDraw()方法中的。下面我们准备来自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次。新建一个CounterView继承自View,代码如下所示:
public class CounterView extends View implements OnClickListener {
private Paint mPaint;
private Rect mBounds;
private int mCount;
public CounterView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBounds = new Rect();
setOnClickListener(this);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(Color.BLUE);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
mPaint.setColor(Color.YELLOW);
mPaint.setTextSize(30);
String text = String.valueOf(mCount);
mPaint.getTextBounds(text, 0, text.length(), mBounds);
float textWidth = mBounds.width();
float textHeight = mBounds.height();
canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2
+ textHeight / 2, mPaint);
}
@Override
public void onClick(View v) {
mCount++;
invalidate();
}
}
可以看到,首先我们在CounterView的构造函数中初始化了一些数据,并给这个View的本身注册了点击事件,这样当CounterView被点击的时候,onClick()方法就会得到调用。而onClick()方法中的逻辑就更加简单了,只是对mCount这个计数器加1,然后调用invalidate()方法。
调用invalidate()方法会导致视图进行重绘,因此onDraw()方法在稍后就将会得到调用。
既然CounterView是一个自绘视图,那么最主要的逻辑当然就是写在onDraw()方法里的了,下面我们就来仔细看一下。这里首先是将Paint画笔设置为蓝色,然后调用Canvas的drawRect()方法绘制了一个矩形,这个矩形也就可以当作是CounterView的背景图吧。接着将画笔设置为黄色,准备在背景上面绘制当前的计数,注意这里先是调用了getTextBounds()方法来获取到文字的宽度和高度,然后调用了drawText()方法去进行绘制就可以了。
这样,一个自定义的View就已经完成了,并且目前这个CounterView是具备自动计数功能的。那么剩下的问题就是如何让这个View在界面上显示出来了,其实这也非常简单,我们只需要像使用普通的控件一样来使用CounterView就可以了。比如在布局文件中加入如下代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.customview.CounterView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true" />
</RelativeLayout>
可以看到,这里我们将CounterView放入了一个RelativeLayout中,然后可以像使用普通控件来给CounterView指定各种属性,比如通过layout_width和layout_height来指定CounterView的宽高,通过android:layout_centerInParent来指定它在布局里居中显示。只不过需要注意,自定义的View在使用的时候一定要写出完整的包名,不然系统将无法找到这个View。
好了,就是这么简单,接下来我们可以运行一下程序,并不停地点击CounterView,效果如下图所示。