今天一个朋友找到我叫我帮做一个app,我拿到界面仔细观察了一下,发现很多界面都是相同的,我第一个念头是写一个布局多处复制粘贴使用就ok,但是越想越感觉这不是一个真正开发者该做的事情,看了下也很简单,就决定自己自定义一个吧。
言归正传,先上图:

是不是很简单?
今天我们先用最简单的自定义view来做,就是布局文件放入viewGroup来打造一个封装好的view,我们开始我们的布局。
先弄明白自定义view的三个构造方法:
public View (Context context)是在java代码创建视图的时候被调用,如果是从xml填充的视图,就不会调用这个
public View (Context context, AttributeSet attrs)这个是在xml创建但是没有指定style的时候被调用
public View (Context context, AttributeSet attrs, int defStyle)给View提供一个基本的style,如果我们没有对View设置某些属性,就使用这个style中的属性。
注意里面的关键字是xml,因为本文章就是用xml来做的。其中的style就用默认的style吧 ,也可以自己定义一个哦
我们先按照步骤继承一个RelativeLayout重写里面的构造方法,由于我们用的是xml,所以我们只需要重写带有两个参数的构造方法
public class ProLinearly extends RelativeLayout {
public ProLinearly(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context, attrs);
}
}
先不管里面的initview方法的实现,我们知道自定义view是可以设置各种属性的,这些属性是定义在什么地方的呢?没错,就是在attrs中,所以在values中新建一个attrs文件,来定义你需要的属性。例如:
<attr name="title" format="string" />
<attr name="titleColor" format="color" />
<attr name="lineColor" format="color" />
<attr name="titleSize" format="dimension" />
<attr name="rootBacColor" format="color" />
<attr name="iconText" format="string" />
<attr name="iconSize" format="dimension" />
<attr name="iconColor" format="color" />
<attr name="lineLeftPadding" format="dimension" />
<attr name="lineRightPadding" format="dimension" />
<attr name="iconTextHide" format="boolean" />
<declare-styleable name="ProLinearly">
<attr name="title" />
<attr name="titleColor" />
<attr name="rootBacColor" />
<attr name="titleSize" />
<attr name="lineColor" />
<attr name="iconText" />
<attr name="iconSize" />
<attr name="iconColor" />
<attr name="lineLeftPadding" />
<attr name="lineRightPadding" />
<attr name="iconTextHide" />
</declare-styleable>
当然你还可以定义更多的属性,这里为了满足需求我们只定义这么多,定义完了属性我们怎么引入自定义view中呢?这就是initview中涉及到的方法了,initView只我们传入了两个参数,第一个上下文,第二个是AttributeSet翻译过来是属性设置,这里的属性就是刚刚我们设置的那些属性,我们就用这个方法来引入属性变量;
TypedArray a = context.getTheme().
obtainStyledAttributes(attrs, R.styleable.ProLinearly,
0, 0);
for (int i = 0; i < a.getIndexCount(); i++) {
int index = a.getIndex(i);
switch (index) {
case R.styleable.ProLinearly_title:
titleText = a.getString(index);
break;
case R.styleable.ProLinearly_iconTextHide:
iconTextHide = a.getBoolean(index, false);
break;
case R.styleable.ProLinearly_titleColor:
titleColor = a.getColor(index, ContextCompat.getColor(context, R.color.default_black));
break;
case R.styleable.ProLinearly_titleSize:
titleSize = a.getDimensionPixelSize(index, 14);
break;
case R.styleable.ProLinearly_lineColor:
lineColor = a.getColor(index, ContextCompat.getColor(context, R.color.default_black));
break;
case R.styleable.ProLinearly_rootBacColor:
rootBacColor = a.getColor(index, ContextCompat.getColor(context, R.color.default_white));
break;
case R.styleable.ProLinearly_iconText:
iconText = a.getString(index);
break;
case R.styleable.ProLinearly_iconColor:
iconColor = a.getColor(index, ContextCompat.getColor(context, R.color.default_white));
break;
case R.styleable.ProLinearly_iconSize:
iconSize = a.getDimensionPixelSize(index, 14);
break;
case R.styleable.ProLinearly_lineLeftPadding:
lineLeftPadding = a.getDimensionPixelSize(index, 0);
break;
case R.styleable.ProLinearly_lineRightPadding:
lineRightPadding = a.getDimensionPixelSize(index, 0);
break;
default:
}
}
a.recycle();
我们并在本地新建引用来保存这个值:
private String titleText;
private int titleSize;
private int titleColor;
private int lineColor;
private int rootBacColor;
private int iconColor;
private String iconText;
private int iconSize;
private int lineLeftPadding;
private int lineRightPadding;
private boolean iconTextHide;
到这里我们属性就引用进来了,下面我们引入我们的布局文件,根据需求我们先新建一个简单的布局文件:

xml中的代码很简单:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:background="@color/default_white"
android:layout_height="50dp">
<android.support.v7.widget.AppCompatTextView
android:layout_marginStart="@dimen/dp_10"
android:textSize="@dimen/sp_16"
android:layout_centerVertical="true"
android:textColor="@color/default_black"
android:gravity="center"
android:id="@+id/title"
tools:text="fdsfsdfsdfsd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="RelativeOverlap" />
<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:id="@+id/icon"
android:layout_marginEnd="@dimen/dp_10"
android:src="@drawable/ic_right"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_height="wrap_content" />
<android.support.v7.widget.AppCompatTextView
tools:text="fdsfsdfsdfsd"
android:id="@+id/icon_content"
android:textSize="@dimen/sp_14"
android:textColor="@color/default_gray"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/icon"
android:layout_marginEnd="@dimen/dp_10"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="RelativeOverlap" />
<View
android:id="@+id/line"
android:background="@color/default_gray_light"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="0.8dp"/>
</RelativeLayout>
然后我们再先前说到的第二个构造方法中读取出布局文件,并且找到里面的控件:
LayoutInflater.from(context).inflate(R.layout.layout_custom,this,true);
并且找到里面的各个控件并且用引用保存起来,到这个时候我们就把先前准别好的属性全部来设置给这些保存的控件:
//背景颜色
root.setBackgroundColor(rootBacColor);
//设置title的样式
title.setTextColor(titleColor);
title.setTextSize(titleSize);
title.setText(titleText);
//iconTitle样式
iconTitle.setText(iconText);
iconTitle.setTextSize(iconSize);
iconTitle.setTextColor(iconColor);
iconTitle.setVisibility(iconTextHide ? GONE : VISIBLE);
//Line的样式
line.setBackgroundColor(lineColor);
LayoutParams layoutParams = (LayoutParams) line.getLayoutParams();
layoutParams.leftMargin = lineLeftPadding;
layoutParams.rightMargin = lineRightPadding;
line.setLayoutParams(layoutParams);
这里贴出initView方法中的全部代码:
private void initView(Context context, AttributeSet attrs) {
View myView = LayoutInflater.from(context).inflate(R.layout.layout_custom, this, true);
RelativeLayout root = myView.findViewById(R.id.root);
AppCompatTextView title = myView.findViewById(R.id.title);
AppCompatTextView iconTitle = myView.findViewById(R.id.icon_content);
View line = myView.findViewById(R.id.line);
TypedArray a = context.getTheme().
obtainStyledAttributes(attrs, R.styleable.ProLinearly,
0, 0);
for (int i = 0; i < a.getIndexCount(); i++) {
int index = a.getIndex(i);
switch (index) {
case R.styleable.ProLinearly_title:
titleText = a.getString(index);
break;
case R.styleable.ProLinearly_iconTextHide:
iconTextHide = a.getBoolean(index, false);
break;
case R.styleable.ProLinearly_titleColor:
titleColor = a.getColor(index, ContextCompat.getColor(context, R.color.default_black));
break;
case R.styleable.ProLinearly_titleSize:
titleSize = a.getDimensionPixelSize(index, 14);
break;
case R.styleable.ProLinearly_lineColor:
lineColor = a.getColor(index, ContextCompat.getColor(context, R.color.default_black));
break;
case R.styleable.ProLinearly_rootBacColor:
rootBacColor = a.getColor(index, ContextCompat.getColor(context, R.color.default_white));
break;
case R.styleable.ProLinearly_iconText:
iconText = a.getString(index);
break;
case R.styleable.ProLinearly_iconColor:
iconColor = a.getColor(index, ContextCompat.getColor(context, R.color.default_white));
break;
case R.styleable.ProLinearly_iconSize:
iconSize = a.getDimensionPixelSize(index, 14);
break;
case R.styleable.ProLinearly_lineLeftPadding:
lineLeftPadding = a.getDimensionPixelSize(index, 0);
break;
case R.styleable.ProLinearly_lineRightPadding:
lineRightPadding = a.getDimensionPixelSize(index, 0);
break;
default:
}
}
a.recycle();
//背景颜色
root.setBackgroundColor(rootBacColor);
//设置title的样式
title.setTextColor(titleColor);
title.setTextSize(titleSize);
title.setText(titleText);
//iconTitle样式
iconTitle.setText(iconText);
iconTitle.setTextSize(iconSize);
iconTitle.setTextColor(iconColor);
iconTitle.setVisibility(iconTextHide ? GONE : VISIBLE);
//Line的样式
line.setBackgroundColor(lineColor);
LayoutParams layoutParams = (LayoutParams) line.getLayoutParams();//注意这里要用view的属性,因为viewgroup中没有leftMargin的属性设置方法
layoutParams.leftMargin = lineLeftPadding;
layoutParams.rightMargin = lineRightPadding;
line.setLayoutParams(layoutParams);
}
这样我们就把属性全部赋值给控件了。到这里就结束了,是不是很简单?但是这里有几个重要的地方,添加的布局一定要放入这个viewGroup中,不然不会显示,这里可以采用两个方法:
方式1:
LayoutInflater.from(context).inflate(R.layout.layout_custom,this,true);
这里面三个参数,第一个布局文件,第二个是放入的viewGroup,第三个是确定放不放进去,我用的这种方式放入。
方式2:
this.addView(myView);
把读取的文件放入这个容器里,最后在布局文件中就可以这样调用:
<com.amijiaoyu.babybus.android.weight.ProLinearly
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:iconTextHide="false"
app:rootBacColor="@color/default_white"
app:lineColor="@color/default_gray_light"
app:lineLeftPadding="@dimen/dp_10"
app:lineRightPadding="@dimen/dp_10"
app:title="链接"
app:iconText="fdsfsfd"
app:iconSize="8sp"
app:iconColor="@color/default_gray"
app:titleColor="@color/default_black"
app:titleSize="6sp" />
最后出来的效果:

我们完全可以加入switch控件,来设置隐藏与显示达到我们第一个图中的要求,操作类似,我这里就不继续贴代码了。
到这里就全部结束了,这就是最简单的自定义view,但是这样有个不好的地方,就是我们把一个布局放入了一个容器,这样写起来很快,但是层级变深了,至于这个问题我们后续继续探讨,还有比较复杂的自定义view其中涉及到了onDraw,onMeasure等等 ,我们后面会继续说到。