自定义控件的方式
- 直接继承重写View&ViewGroup。这种方式是最灵活的,可以定制任何你想要的控件效果。
- 扩展已有控件。例如我们需要为文本添加特别的下划线,那么可以扩展TextView,绘制特殊的下划线。
- 组合已有控件。例如标题样式是通用的,一个标题一个返回按钮,我们可以组合这两个控件,对外提供更方便的调用,容易维护。
直接继承重写View&ViewGroup
重写View&ViewGroup的流程
1.重写onMeasure(int widthMeasureSpec, int heightMeasureSpec)。确定控件的大小。
2.重写onLayout(boolean changed, int left, int top, int right, int bottom) 。确定控件的位置。
3.重写onDraw(Canvas canvas)。绘画控件的内容。
自定义控件一般都需要重写onMeasure(..)确定控件的大小,如果容器控件就确定子控件的大小,如果是非容器控件就确定控件本身的大小。容器类控件主要重写onLayout(..)方法确定子控件的摆放位置。自定义非容器控件的话,那么主要重写onDraw(..)方法确定控件内容。
交通路网控件的例子
这是一个显示交通路网状况的控件,用不同颜色和百分比显示路况状况。这个控件的实现主要重写了onDraw()绘画了路况状况内容。
1、定义数据结构
public class RoadStatusView extends View {
private List<StatusInfo> mList = new ArrayList<>();
//.....
private boolean isAdjust= true;
public void clear(){
mList.clear();
}
public void addStatus(StatusInfo statusInfo){
mList.add(statusInfo);
}
public static class StatusInfo {
public int count;//数量
public int resColor;//本地颜色值
}
//状态辅助绘画类
private class StatusMeasure{
public StatusMeasure(int start,int end,String text,int resColor){
this.start = start;
this.end = end;
this.text = text;
this.resColor = resColor;
}
public int start;
public int end;
public String text;
public int resColor;
}
}
2、初始化画笔。自定义控件过程中,有两个十分重要的类。一个是Paint,负责确定绘画内容,一个是Canvas,负责把画笔的内容绘制到屏幕。
public class RoadStatusView extends View {
//....
private Paint mPaint;//通用画笔
private TextPaint mTextPaint;// 文字画笔
public RoadStatusView(Context context) {
super(context);
init();
}
public RoadStatusView(Context context, AttributeSet attrs) {
super(context, attrs);
//初始化画笔
init();
}
private void init(){
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);//填充和绘制边框
mTextPaint = new TextPaint();
mTextPaint.setColor(getResources().getColor(R.color.white));//文字颜色
mTextPaint.setTextSize(dip2px(getContext(),13));//文字大小
mTextPaint.setAntiAlias(true);//抗锯齿
}
}
3、绘制颜色块与比例文字
public class RoadStatusView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取onMeasure()测量的宽度,由于这里没有重写,所以这里的大小是父容器的宽度。
int viewWidth= getMeasuredWidth();
//计算状态数量和所占实际宽度的比重
int statusCount = 0;
for (StatusInfo statusInfo:mList) {
statusCount+= statusInfo.count;
}
int start= 0;
float ratio = (float) ((viewWidth*1.0)/statusCount);
int drawStart = 0;
int drawEnd = 0;
//根据上面计算出的状态数量和所占的比重计算出宽度的起点和结束位置。
List<StatusMeasure> measures = new ArrayList<>();
for (StatusInfo statusInfo:mList) {
drawStart = start;
drawEnd = (int) (start+(ratio*1.0*statusInfo.count));
measures.add(new StatusMeasure(drawStart,drawEnd,(int)((statusInfo.count*1.0/statusCount)*100)+"%",statusInfo.resColor));
start+= (int) (ratio*1.0*statusInfo.count);
}
for(StatusMeasure statusMeasure :measures){
//遍历绘制颜色块和比例文字
realDraw(canvas,statusMeasure.start,statusMeasure.end,statusMeasure.resColor,statusMeasure.text);
}
}
private void realDraw(Canvas canvas,int start,int end,int resColor,String text){
mPaint.setColor(resColor);
//绘画方块
canvas.drawRect(start,0,end,getHeight(),mPaint);
float width = Layout.getDesiredWidth(text,mTextPaint);//获取文字宽度
int startX = (int) ((end-start)/2-width/2)+start;
Rect rect = new Rect();
mTextPaint.getTextBounds(text,0, text.length(),rect);//获取文字的高度
int startY = (getBottom() -getTop())/2+(rect.bottom-rect.top)/2;
canvas.drawText(text,startX,startY,mTextPaint);/将文字绘画在方块的中间。
}
}
组合控件
组合控件的开发流程
1.编写组合控件的内容。可以通过XML布局编写组合控件的内容,也可以通过硬编码编写。
2.继承ViewGroup的子类,例如FrameLayout。并将组合控件内容添加到里面。
3.定义对外调用的XML属性和对外调用方法。
4.引用自定义控件。
个人资料填写项的例子
这是一个很常见的输入项的组合控件,左边是描述项右边是输入内容。
1、编写组合控件的布局
view_input_item.xml
<?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="wrap_content"
android:orientation="vertical"
android:background="@color/white">
<LinearLayout
android:layout_below="@+id/viewBottom"
android:paddingTop="5dp"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:paddingBottom="5dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textSize="16sp"
android:text="描述:"
android:paddingBottom="5dp"
android:layout_marginLeft="12dp"
android:textColor="@color/gray"
android:paddingTop="5dp" />
<EditText
android:id="@+id/et"
android:textColor="@color/text"
android:textSize="16sp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:background="@null"
android:text="请输入内容"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:id="@+id/viewBottom"
android:background="@color/div"
android:layout_height="1px"
android:layout_width="match_parent"/>
</LinearLayout>
2、继承FrameLayout,并将组合布局添加到FrameLayout里面去。
InputView.java
/**
* Created by Zhouztashin on 2016/1/12.
* 输入项View
*/
public class InputView extends FrameLayout {
private TextView tvDescribe;//描述
private EditText et;//输入项
private TextView tvStar;//必填项标识
public InputView(Context context) {
super(context);
initView();
}
public InputView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView(){
View v = View.inflate(getContext(), R.layout.view_input_item, this);//将自定义组件添加到容器中。
tvDescribe = (TextView) v.findViewById(R.id.tvText);
tvStar = (TextView) v.findViewById(R.id.tvStar);
et = (EditText) v.findViewById(R.id.et);
viewBottom = v.findViewById(R.id.viewBottom);
}
}
3、定义XML属性
input_view_attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="InputView">
<attr name="describe" format="reference|string"/>
<attr name="showStar" format="boolean"></attr>
</declare-styleable>
</resources>
4、定义对外调用方法
public class InputView extends FrameLayout {
public InputView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
//...获取XML属性值,并设置
TypedArray typedArray = getResources().obtainAttributes(attrs, R.styleable.InputView);
//描述
String describe = typedArray.getString(R.styleable.InputView_describe);
setDescribe(describe);
boolean showStar = typedArray.getBoolean(R.styleable.InputView_showStar,false);
showStar(showStar);
}
//设置描述
public void setDescribe(String text){
tvDescribe.setText(text);
}
//是否显示必填项的星号
public void showStar(boolean b){
if(b){
tvStar.setVisibility(View.VISIBLE);
}else{
tvStar.setVisibility(View.INVISIBLE);
}
}
5、引用自定义控件
user_info.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:inputView="http://schemas.android.com/apk/res-auto" <!-- 这里声明命名空间,下面需要引用到 -->
android:fitsSystemWindows="true"
android:orientation="vertical"
android:background="@color/light_white"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.common.ui.widget.InputView
android:id="@+id/ipvAge"
inutView:showStar="true" <!-- 设置属性 -->
inutView:describe="年龄"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout