自己动手写代码是学习编程的最好方式。接下来我通过一个小例子来学习如何创建简单的自定义view。这次的例子是组合view。需要实现的是类似名片的view,如下图所示。
因为是组合已有widget,所以要做的是选取需要的widget在自定义的view中进行组合,然后通过代码控制它的行为,步骤如下:
1. 编写自定义View的layout文件
2. 定义可在xml文件中设置的属性
3. 定义对应的Class文件
4. 测试
我们一步一步来实现。
1. 编写自定义View的layout文件
这个view比较简单,我们可以通过组合ImageView和TextView来实现,使用RelativeLayout作为container, layout文件如下:
file name : name_card.xml
<?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">
<ImageView
android:id="@+id/photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"/>
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:textSize="18sp"
android:layout_toRightOf="@+id/photo"/>
<View
android:id="@+id/ori_line"
android:background="@color/gray"
android:layout_width="match_parent"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_height="0.5dp"
android:layout_alignLeft="@+id/name"
android:layout_below="@+id/name"/>
<TextView
android:id="@+id/title"
android:layout_alignLeft="@+id/name"
android:layout_below="@+id/ori_line"
android:textSize="24sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
2. 定义可在xml文件中设置的属性
可以想到,我们需要用户可以在layout文件中设置头像,名字和头衔,所以,我们创建res\values\attrs.xml文件,并定义属性:<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AttrNameCard">
<attr name="ncName" format="string" />
<attr name="ncTitle" format="string" />
<attr name="ncPhoto" format="integer"/>
</declare-styleable>
</resources>
主要这个styleable的名字AttrNameCard,在接下来的代码中会用到。
3. 定义对应的Class文件
我们将这个自定义view命名为NameCard,让它继承FrameLayout类。我们先定义成员变量:
private ImageView ivPhoto;
private TextView tvName;
private TextView tvTitle;
private int idName;
private int idTitle;
private int idPhoto;
它们对应view中的widget和用户定义的属性值。然后,定义构造函数:
public NameCard(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.name_card, this);
ivPhoto = (ImageView) findViewById(R.id.photo);
tvName = (TextView) findViewById(R.id.name);
tvTitle = (TextView) findViewById(R.id.title);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.AttrNameCard,
0, 0);
try {
idName = a.getResourceId(R.styleable.AttrNameCard_ncName, R.string.default_name);
idTitle = a.getResourceId(R.styleable.AttrNameCard_ncTitle, R.string.default_title);
idPhoto = a.getResourceId(R.styleable.AttrNameCard_ncPhoto, R.drawable.ic_person_black_36dp);
ivPhoto.setImageDrawable(getResources().getDrawable(idPhoto));
tvName.setText(idName);
tvTitle.setText(idTitle);
} finally {
a.recycle();
}
}
在构造函数中,我们加载了自定义view的layout文件,然后读取attribute值,在设置给对应的widget。接下来定义接口用来动态设置属性:
public void setPhoto(Drawable drawable) {
ivPhoto.setImageDrawable(drawable);
}
public void setName(String name) {
tvName.setText(name);
}
public void setTitle(String title) {
tvTitle.setText(title);
}
完整的代码如下:
package com.sample.j.lib;
public class NameCard extends FrameLayout {
private ImageView ivPhoto;
private TextView tvName;
private TextView tvTitle;
private int idName;
private int idTitle;
private int idPhoto;
public NameCard(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.name_card, this);
ivPhoto = (ImageView) findViewById(R.id.photo);
tvName = (TextView) findViewById(R.id.name);
tvTitle = (TextView) findViewById(R.id.title);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.AttrNameCard,
0, 0);
try {
idName = a.getResourceId(R.styleable.AttrNameCard_ncName, R.string.default_name);
idTitle = a.getResourceId(R.styleable.AttrNameCard_ncTitle, R.string.default_title);
idPhoto = a.getResourceId(R.styleable.AttrNameCard_ncPhoto, R.drawable.ic_person_black_36dp);
ivPhoto.setImageDrawable(getResources().getDrawable(idPhoto));
tvName.setText(idName);
tvTitle.setText(idTitle);
} finally {
a.recycle();
}
}
public void setPhoto(Drawable drawable) {
ivPhoto.setImageDrawable(drawable);
}
public void setName(String name) {
tvName.setText(name);
}
public void setTitle(String title) {
tvTitle.setText(title);
}
}
这样,我们的自定义view就完成了。
4. 测试
效果如何,还要看测试。创建一个Activity,在对应的layout中使用自定义view。
<LinearLayout
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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.sample.j.namecard.MainActivity">
<com.sample.j.lib.NameCard
android:id="@+id/nc_tom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
custom:ncPhoto="@drawable/ic_person_black_36dp"
custom:ncName="@string/name_tom"
custom:ncTitle="@string/title_tom"/>
<Button
android:id="@+id/change_info"
android:onClick="onClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="change info"/>
</LinearLayout>
xmlns:custom="http://schemas.android.com/apk/res-auto"指示在这个layout中使用自定义的属性。
custom:ncPhoto="@drawable/ic_person_black_36dp"
custom:ncName="@string/name_tom"
custom:ncTitle="@string/title_tom"
为自定义view设置属性。
Activiy文件功能很简单,加载layout,点击button之后改变自定义view的值。
public class MainActivity extends AppCompatActivity {
private NameCard mNameCard;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNameCard = (NameCard) findViewById(R.id.nc_tom);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_info:
mNameCard.setName("Mary King");
mNameCard.setTitle("QA Engineer");
mNameCard.setPhoto(getResources()
.getDrawable(R.drawable.ic_person_outline_black_36dp));
break;
default:
break;
}
}
}
下面是activity启动后的截图,
点击button之后的截图:
测试完毕。
5. 代码地址
完整代码的地址请点击
这里
----- over -----