前言
Android 项目中避免不了会使用自定义控件,主要能够避免代码的冗余,使用起来也很灵活,而且也方便后期移植入其他项目。自定义控件也是面试中经常问到的东西,写过挺多自定义控件但是一直没有系统的总结过,今天再重新系统的学习一下。
自定义控件主要分为三类,自定义组合控件,自定义绘制控件,自定义继承控件。
1.自定义组合控件是项目中经常会用到,像标题栏,复用率较高的布局。
2.自定义绘制控件是面试中经常会问到的,如果需求复杂,绘制的流程也会难度加大(后期会详细介绍自定义控件的绘制)
3.自定义继承控件主要是针对Android 原生的控件进行扩展,像自定义listView 。
下面我们来详细说一下这三种自定义控件
一.自定义组合控件
组合控件的意思就是,我们并不需要自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但我们可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。
场景:一般一个项目中所有页面的标题栏大体都差不多,所以我们没有必要去每个页面都重写写布局,这个时候我们就可以用自定义组合控件
1.第一步:新建一个title.xml布局文件,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/lay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<!--返回键-->
<ImageView
android:id="@+id/title_icon"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:paddingLeft="15dp"
android:src="@mipmap/black_back" />
<!--关闭按钮-->
<TextView
android:id="@+id/tv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingLeft="15dp"
android:text="关闭"
android:textColor="#333333"
android:textSize="15sp"
android:visibility="gone" />
<!--标题-->
<TextView
android:id="@+id/title_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="13dp"
android:layout_marginTop="13dp"
android:ellipsize="end"
android:maxLength="13"
android:singleLine="true"
android:textColor="#010101"
android:textSize="17sp" />
<!--右侧提示字和图标-->
<LinearLayout
android:id="@+id/ll_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<!--图标-->
<ImageView
android:id="@+id/iv_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="3dp" />
<!--提示文字-->
<TextView
android:id="@+id/tv_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="15dp"
android:textColor="#333333"
android:textSize="15sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@+id/title_name"
android:background="@color/line_d7" />
</RelativeLayout>
UI效果如图
2.第二步:接下来我们来创建一个View 集成RelativeLayout 如
public class WhitePublicTitleView extends RelativeLayout {
View mView;
ImageView title_icon;
TextView title_name;
RelativeLayout lay;
ImageView iv_right;
TextView tv_right;
LinearLayout ll_right;
TextView tv_close;
public WhitePublicTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WhitePublicTitleView(Context context) {
super(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mView = LayoutInflater.from(getContext()).inflate(R.layout.public_title_view_white, this);
initView();
}
public void setTitleBg() {
lay.setBackgroundColor(Color.parseColor("#de3031"));
}
public void initView() {
title_icon = (ImageView) mView.findViewById(R.id.title_icon);
title_name = (TextView) mView.findViewById(R.id.title_name);
lay = (RelativeLayout) mView.findViewById(R.id.lay);
iv_right = (ImageView) mView.findViewById(R.id.iv_right);
tv_right = (TextView) mView.findViewById(R.id.tv_right);
tv_close = (TextView) mView.findViewById(R.id.tv_close);
ll_right = (LinearLayout) mView.findViewById(R.id.ll_right);
}
public void setBackListener(OnClickListener clickListener) {
title_icon.setOnClickListener(clickListener);
tv_close.setOnClickListener(clickListener);
}
public void setBackState(int state) {
title_icon.setVisibility(state);
}
public void setTitleNam(String name) {
title_name.setText(name);
}
public void setRight(String state, int icon) {
tv_right.setText(state);
iv_right.setImageResource(icon);
}
public void setRightListener(OnClickListener clickListener) {
ll_right.setOnClickListener(clickListener);
}
//展示文字关闭按钮
public void showTextClose() {
tv_close.setVisibility(View.VISIBLE);
title_icon.setVisibility(View.GONE);
}
}
3.第三步:我们使用自定义组合控件
布局文件引入
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/f2"
android:divider="@drawable/divider"
android:orientation="vertical">
<!--标题-->
<com.jyjt.ydyl.Widget.WhitePublicTitleView
android:id="@+id/title_collect"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
代码初始化具体数据
title_collect= (ImageView) mView.findViewById(R.id.title_collect);
title_collect.setTitleNam("我的收藏");
title_collect.setBackListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SwitchActivityManager.exitActivity(MyCollectActivity.this);
}
});
总结:自定义控件在创建和使用起来都很简单。
二.自定义绘制控件
自定义绘制控件的意思就是,这个View上所展现的内容全部都是我们自己绘制出来的。绘制的代码是写在onDraw()方法中的
场景:自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次。新建一个CounterView继承自View,代码如下所示:
1.第一步:创建一个类集成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();
}
}
可以看到,首先我们在onClick()方法调用调用invalidate()方法会导致视图进行重绘,因此onDraw()方法在稍后就将会得到调用。
主要的逻辑当然就是写在onDraw()方法里首先是将Paint画笔设置为蓝色,然后调用Canvas的drawRect()方法绘制了一个矩形背景,接着将画笔设置为黄色,绘制当前的计数,注意这里先是调用了getTextBounds()方法来获取到文字的宽度和高度,然后调用了drawText()方法去进行绘制。
2.第二步:使用自定义绘制控件
<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。
三.自定义继承控件
继承控件,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能。
场景:我们做一个简单的自定义一个diaolog ,点击确定按钮消失,(主要集成具体的原生控件)
第一步:创建一个布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/confirm_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_corners_white"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="5dip"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="18dp"
android:layout_marginTop="24dp"
android:src="@mipmap/ic_build_dialog" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dip"
android:background="@color/line" />
<Button
android:id="@+id/ok_btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/commenttext"
android:gravity="center"
android:text="知道了"
android:textColor="@color/de30"
android:textSize="16sp" />
</LinearLayout>
第二部:创建一个类继承Dialog
public class BuildingDialog extends Dialog {
public View mView;
LayoutInflater inflater;
Button ok_btn;
public Context mContext;
DialogCallBack mDialogCallBack;
public BuildingDialog(Context context) {
this(context, R.style.dialog, null);
this.mContext = context;
}
public BuildingDialog(Context context, int themeResId, String content) {
super(context, themeResId);
this.mContext = context;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
inflater = LayoutInflater.from(mContext);
mView = inflater.inflate(R.layout.dialog_build, null);
setContentView(mView);
initView();
ok_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
// mDialogCallBack.clickokBtn();
}
});
}
public void initView() {
ok_btn = (Button) mView.findViewById(R.id.ok_btn);
}
public void setDialogCallBack(DialogCallBack dialogCallBack) {
this.mDialogCallBack = dialogCallBack;
}
public interface DialogCallBack {
void clickokBtn();
}
}
第三步:使用自定义继承控件
BuildingDialog mBuildingDialog= new BuildingDialog(mContext);
mBuildingDialog.show(); //展示
mBuildingDialog.setDialogCallBack(this); //回调监听
四.自定义属性
使用自定义控件的时候有时会创建自己的控件的属性,下面我们来讲讲自定义属性怎么用
主要步骤
1.定义declare-styleable,添加attr
2.使用TypedArray获取自定义属性
3.设置到View上
第一步:定义declare-styleable,添加attr
第二步:稍后更新
第三步:稍后更新
总结:
虽然每个例子都很简单,但是万变不离其宗,复杂的View也是由这些简单的原理堆积出来的。后期会写深入了解View系列的文章,感谢大家有耐心看到最后。