您的位置:首页 > 移动开发 > Android开发

【Android】Android自定义控件详解

2016-02-26 00:56 489 查看
开学前提前来校,结束了寒假学习的尾巴工作。现在可以有时间来啃《Android群英传》,所以还是老习惯,在md上做好笔记写好总结。第三章主要讲自定义View。先把源码贴出来供大家参考与fork哈哈。

—-Android自定义View源码github地址,欢迎fork—-

Activity的setContentView()方法将布局显示到手机屏幕上的实现原理

每个
Activity
都有一个
Window
对象,这个对象由
PhoneWindow
来实现,
PhoneWindow
DecorView
设置为整个应用窗口的根
View
DecorView
作为窗口界面的顶层视图,通过其封装的一系列方法就可以操作整个窗口。

View的测量

Android中
View
的测量在
onMeasure()
方法中进行,方法接收两个参数
int widthMeasureSpec, int heightMeasureSpec
,分别表示宽度
MeasureSpec
对象和高度
MeasureSpec
对象,这里涉及到
MeasureSpec
类,
MeasureSpec
对象是一个32位的
int
值,其中高2位表示测量的模式,即
EXACTY
,
AT_MOSTT
,和
UNSPEECIFIED
三种:

EXACTY
表示精确模式,当控件的
layout_width
layouut_height
属性被指定为具体的数值时,或者指定为
match_parent
时,系统使用的是
EXACTY
模式;

AT_MOST
表示最大值模式,当控件的
layout_width
layouut_height
属性被指定为wrap_content时,只要空间的尺寸不超过父控件的尺寸,就会随着内容的大小而变化;

UNSPEECIFIED
表示不指定大小,这种模式通常在自定义View的时候才使用。

View
类默认的
onMeasure()
方法只支持
EXACTY
模式,自定义控件的时候需要重写
onMeasure()
方法。通过
Ctrl+鼠标左键
点进去,我们知道
onMeasure()
方法底层调用的是
setMeasuredDimension()
方法。所以在
onMeasure()
方法中,我们需要把测量值传给
setMeasuredDimension()
方法。然而
int widthMeasureSpec, int heightMeasureSpec
两个参数都是默认值,所以我们需要根据测量模式来重新确定测量值,就是下面的自定义的
measureHeight()
方法、
measureWidth()
方法。代码的解释都已经注释,如下:

/**
* 下面是自定义的measureHeight()方法、measureWidth()方法实现对宽、高值的自定义
*
* @param measureSpec
* @return
*/
private int measureWidth(int measureSpec) {
int result = 0;
// MeasureSpec.getMode()提取measureSpec对象中包含的测量模式
int specMode = MeasureSpec.getMode(measureSpec);
// MeasureSpec.getSize()提取measureSpec对象中包含的测量值大小
int specSize = MeasureSpec.getSize(measureSpec);

// 判断测量类型,根据不同的测量模式specMode来指定不同的测量值specSize
if (specMode == MeasureSpec.EXACTLY) {
// 模式为EXACTLY,直接使用指定的specSize
result = specSize;
} else {
// 否则,需要制定默认的测量值为200
result = 200;
// 判断,如果测量模式为AT_MOST,测量值指定为result, specSize中的最小值
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}

/**
* measureHeight()方法的含义同上面的measureWidth()一样
*
* @param measureSpec
* @return
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}


所以重写的
onMeasure()
方法代码就很简单啦,只需要在
setMeasuredDimension()
方法中调用自定义的
measureHeight()
方法、
measureWidth()
方法计算测量值就可以啦。代码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}


View的绘制

然后简单的重写
View
类的
onDraw()
方法就可以在
Canvas
对象上面绘制自定义
View
啦。可以看到
onDraw(Canvas canvas)
方法接收一个Canvas对象,创建
Canvas
对象代码如下:

Canvas canvas = new Canvas(bitmap);


具体怎么使用
Canvas
对象来绘制自定义View,请参考我的另外一篇博客

《Android图片资源的加载、简单处理》

这里直接贴出代码如下:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GRAY);
int width = getWidth();
int height = getHeight();
Log.d("wondertwo", "width : " + width + " height : " + height);
}


ViewGroup的测量

ViewGroup
控件作为父控件可以包含多个
View
控件,并可以管理它包含的所有View控件。当
ViewGroup
的大小指定为
wrap_content
的时候,
ViewGroup
会对子
View
进行遍历,通过调用子
View
Measure
方法获得所有
View
的大小,从而来决定自己的大小。其他情况时则会通过具体的指定值来设置自己的大小。然后
ViewGroup
遍历所有字
View
Layout
方法来把他们放到指定的位置上,自定义
View
就是通过重写
onLayout()
方法来实现控制子
View
显示位置的逻辑。同样,如果需要支持
wrap_content
属性,还必须重写
onMeasure()
方法才可以。

自定义View

通常实现自定义
View
有三种方法:

对现有控件进行拓展

通过组合来实现新控件

重写
View
来实现全新控件

涉及到
View
中比较重要的几个回调方法:

onFinishInflate()----从XML加载组件后回调
onSizeChanged()----组件大小改变时回调
onMeasure()----回调该方法来进行View组件的测量
onLayout()----回调该方法来显示View组件的显示位置
onTouchEvent()----监听触摸事件


对现有控件进行拓展:

TextView
控件为例来展开说明,
TextView
之所以能够显示文本信息是因为调用了
TextView
类的
onDraw()
方法来把文本信息绘制到
View
控件上,代码如下所示:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}


我们可以通过在
super.onDraw(canvas)
的前面或者后面加上我们自己的控制逻辑,来实现在绘制文本信息的前后完成自定义的操作。如下所示:

@Override
protected void onDraw(Canvas canvas) {
// 回调父类方法之前实现自己的逻辑,对TextView来说实在绘制文本之前
super.onDraw(canvas);
// 回调父类方法之前实现自己的逻辑,对TextView来说实在绘制文本之后
}


比如我们可以利用Android 中
Paint
对象的Shader渲染器来实现
TextView
文字闪动的效果。要实现这个效果首先通过
getPaint()
方法获取
Paint
对象
mPaint = getPaint()
,然后通过
mPaint.setShader(mLinearGradient)
把定义好的Shader效果通过属性
LinearGradient
设置给Paint对象。示例代码如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0) {
mViewWidth = getMeasuredWidth();
if (mViewWidth > 0) {
mPaint = getPaint();
mLinearGradient = new LinearGradient(
0,
0,
mViewWidth,
0,
new int[]{
Color.BLUE, 0xffffffff,
Color.BLUE},
null,
Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
mGradientMatrix = new Matrix();
}
}
}


然后在
onDraw()
方法中通过矩阵不断平移渐变效果,从而产生动态的闪动效果。
onDraw()
方法代码如下:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mGradientMatrix != null) {
mTranslate += mViewWidth / 5;
if (mTranslate > 2 * mViewWidth) {
mTranslate = -mViewWidth;
}
mGradientMatrix.setTranslate(mTranslate, 0);
mLinearGradient.setLocalMatrix(mGradientMatrix);
postInvalidateDelayed(100);
}
}


创建复合控件

这种方式需要继承一个合适的
ViewGroup
,再给他添加指定功能的控件,从而组合成新的复合控件。并指定一些可配置的属性让它具有更强的扩展性。一般分为定义属性,组合控件,引用模板三步。下面是以
TopBar
,一个标题栏自定义控件为例。自定义控件的实现思路是:

定义属性就是把要自定义的控件的所有的属性都定义在在
res/values
目录下的
attrs.xml
配置文件中,然后在控件类
TopBar
类中获取定义的属性值。

组合控件就是通过在
TopBar
类的构造方法中动态获取要组合的控件的实例化对象。比如按钮、
TextView
的实例等等,把获取到的定义好的属性值设置给控件对象,并且在
TopBar
类中定义接口暴露给空间的调用者。

引用模板也就是使用我们的自定义控件。只是我们平时是大多都是引用系统提供的系统组件,引用自定义控件和引用系统组件的区别是要定义第三方控件的命名空间,并且要使用包名全路径。

res/values
目录下创建一个
attrs.xml
配置文件来定义相关属性,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
declare-styleable标签声明使用自定义属性
name属性确定引用名称
<attr>标签声明具体的自定义属性,比如字体、大小、颜色、背景
format属性指定自定义属性的类型
-->
<declare-styleable name="TopBar">
<attr name="title" format="string"/>
<attr name="titleTextSize" format="dimension"/>
<attr name="titleTextColor" format="color"/>
<attr name="leftTextColor" format="color"/>
<attr name="leftBackground" format="reference|color"/>
<attr name="leftText" format="string"/>
<attr name="rightTextColor" format="color"/>
<attr name="rightBackground" format="integer|color"/>
<attr name="rightText" format="string"/>
</declare-styleable>
</resources>


以上各个标签的含义是:

declare-styleable标签声明使用自定义属性

name属性确定引用名称

标签声明具体的自定义属性,比如字体、大小、颜色、背景

format属性指定自定义属性的类型

然后是创建
TopBar
控件类继承
Relativelayout
。在其构造方法中实现组合控件的逻辑。
TopBar
控件类代码如下:

package com.wondertwo.app.application;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;

/**
* Created by Allenieo on 2016/2/25.
*/
public class TopBar extends RelativeLayout {

// 包含topbar上的元素:左按钮、右按钮、标题
private Button mLeftButton, mRightButton;
private TextView mTitleView;

// 布局属性,用来控制组件元素在ViewGroup中的位置
private LayoutParams mLeftParams, mTitlepParams, mRightParams;

// 左按钮的属性值,即我们在atts.xml文件中定义的属性
private int mLeftTextColor;
private Drawable mLeftBackground;
private String mLeftText;
// 右按钮的属性值,即我们在atts.xml文件中定义的属性
private int mRightTextColor;
private Drawable mRightBackground;
private String mRightText;
// 标题的属性值,即我们在atts.xml文件中定义的属性
private float mTitleTextSize;
private int mTitleTextColor;
private String mTitle;

// 映射传入的接口对象
private topbarClickListener mListener;

public TopBar(Context context) {
super(context);
}

public TopBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public TopBar(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundColor(0xFFF59563);
// 通过这个方法,将在attrs中定义的declare-styleable的所有属性的值存储到TypedArray中
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);

// 从TypedArray中取出相应的值来为要设置的属性赋值
mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, 0);
mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground);
mLeftText = ta.getString(R.styleable.TopBar_leftText);
mRightTextColor = ta.getColor(R.styleable.TopBar_rightTextColor, 0);
mRightBackground = ta.getDrawable(R.styleable.TopBar_rightBackground);
mRightText = ta.getString(R.styleable.TopBar_rightText);
mTitleTextSize = ta.getDimension(R.styleable.TopBar_titleTextSize, 10);
mTitleTextColor = ta.getColor(R.styleable.TopBar_titleTextColor, 0);
mTitle = ta.getString(R.styleable.TopBar_title);

// 获取完TypedArray的属性值后,一般要调用recyle方法来避免重新创建的时候发生错误
ta.recycle();

// 开始组合控件
mLeftButton = new Button(context);
mRightButton = new Button(context);
mTitleView = new TextView(context);

// 用我们引用的属性值来为左、右的点击按钮和中间的标题栏三个组件元素赋值
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText);

mRightButton.setTextColor(mLeftTextColor);
mRightButton.setBackground(mRightBackground);
mRightButton.setText(mRightText);

mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleTextColor);
mTitleView.setTextSize(mTitleTextSize);
mTitleView.setGravity(Gravity.CENTER);

// 为组件元素设置相应的布局元素
mLeftParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
// 添加到ViewGroup
addView(mLeftButton, mLeftParams);

mRightParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);
addView(mRightButton, mRightParams);

mTitlepParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
addView(mTitleView, mTitlepParams);

// 按钮的点击事件,不需要具体的实现,只需调用接口的方法,回调时再具体去实现
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.leftClick();
}
});
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.rightClick();
}
});
}

/**
* 设置按钮的显示与否,通过id区分按钮,flag区分是否显示
*
* @param id
* @param flag
*/
public void setButtonVisable(int id, boolean flag) {
if (flag) {
if (id == 0) {
mLeftButton.setVisibility(View.VISIBLE);
} else {
mRightButton.setVisibility(View.VISIBLE);
}
} else {
if (id == 0) {
mLeftButton.setVisibility(View.GONE);
} else {
mRightButton.setVisibility(View.GONE);
}
}
}

/**
* 暴露一个方法给调用者来注册接口回调,通过接口来获得回调者对接口方法的实现
*
* @param mListener
*/
public void setOntopbarClickListener(topbarClickListener mListener) {
this.mListener = mListener;
}

/**
* 接口对象,实现回调机制,在回调方法中通过映射的接口对象调用接口中的
* 方法,而不用考虑具体实现,具体实现由回调者自己去实现
*/
public interface topbarClickListener {
// 左按钮点击事件
void leftClick();
// 右按钮点击事件
void rightClick();
}

}


组合控件要完成三件事情:

获取定义好的属性,比如颜色,字体,等属性值;

创建要添加到自定义控件中的组件的实例化对象,比如
Button
TextView
等等组件。并把获取的属性值设置给这些组件的实例化对象;

封装接口,让自定义控件的调用者去实现具体逻辑;

系统提供了
TypedArray
这个
api
来获取自定义属性集,
R.styleable.TopBar
就是
attrs
文件
<declare-styleable name= "TopBar">
标签指定的
name
。获取代码如下:

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);


先来看看
TopBar
类中定义的接收变量有哪些:

// 包含topbar上的元素:左按钮、右按钮、标题
private Button mLeftButton, mRightButton;
private TextView mTitleView;

// 布局属性,用来控制组件元素在ViewGroup中的位置
private LayoutParams mLeftParams, mTitlepParams, mRightParams;

// 左按钮的属性值,即我们在atts.xml文件中定义的属性
private int mLeftTextColor;
private Drawable mLeftBackground;
private String mLeftText;
// 右按钮的属性值,即我们在atts.xml文件中定义的属性
private int mRightTextColor;
private Drawable mRightBackground;
private String mRightText;
// 标题的属性值,即我们在atts.xml文件中定义的属性
private float mTitleTextSize;
private int mTitleTextColor;
private String mTitle;

// 映射传入的接口对象
private topbarClickListener mListener;


再看看
TopBar
类的完整代码如下:

package com.wondertwo.app.application;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;

/**
* Created by Allenieo on 2016/2/25.
*/
public class TopBar extends RelativeLayout {

// 包含topbar上的元素:左按钮、右按钮、标题 private Button mLeftButton, mRightButton; private TextView mTitleView; // 布局属性,用来控制组件元素在ViewGroup中的位置 private LayoutParams mLeftParams, mTitlepParams, mRightParams; // 左按钮的属性值,即我们在atts.xml文件中定义的属性 private int mLeftTextColor; private Drawable mLeftBackground; private String mLeftText; // 右按钮的属性值,即我们在atts.xml文件中定义的属性 private int mRightTextColor; private Drawable mRightBackground; private String mRightText; // 标题的属性值,即我们在atts.xml文件中定义的属性 private float mTitleTextSize; private int mTitleTextColor; private String mTitle; // 映射传入的接口对象 private topbarClickListener mListener;
public TopBar(Context context) {
super(context);
}

public TopBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public TopBar(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundColor(0xFFF59563);
// 通过这个方法,将在attrs中定义的declare-styleable的所有属性的值存储到TypedArray中
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
// 从TypedArray中取出相应的值来为要设置的属性赋值
mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, 0);
mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground);
mLeftText = ta.getString(R.styleable.TopBar_leftText);
mRightTextColor = ta.getColor(R.styleable.TopBar_rightTextColor, 0);
mRightBackground = ta.getDrawable(R.styleable.TopBar_rightBackground);
mRightText = ta.getString(R.styleable.TopBar_rightText);
mTitleTextSize = ta.getDimension(R.styleable.TopBar_titleTextSize, 10);
mTitleTextColor = ta.getColor(R.styleable.TopBar_titleTextColor, 0);
mTitle = ta.getString(R.styleable.TopBar_title);

// 获取完TypedArray的属性值后,一般要调用recyle方法来避免重新创建的时候发生错误
ta.recycle();

// 开始组合控件
mLeftButton = new Button(context);
mRightButton = new Button(context);
mTitleView = new TextView(context);

// 用我们引用的属性值来为左、右的点击按钮和中间的标题栏三个组件元素赋值
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText);

mRightButton.setTextColor(mLeftTextColor);
mRightButton.setBackground(mRightBackground);
mRightButton.setText(mRightText);

mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleTextColor);
mTitleView.setTextSize(mTitleTextSize);
mTitleView.setGravity(Gravity.CENTER);

// 为组件元素设置相应的布局元素
mLeftParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
// 添加到ViewGroup
addView(mLeftButton, mLeftParams);

mRightParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);
addView(mRightButton, mRightParams);

mTitlepParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
addView(mTitleView, mTitlepParams);

// 按钮的点击事件,不需要具体的实现,只需调用接口的方法,回调时再具体去实现
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.leftClick();
}
});
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.rightClick();
}
});
}

/**
* 设置按钮的显示与否,通过id区分按钮,flag区分是否显示
*
* @param id
* @param flag
*/
public void setButtonVisable(int id, boolean flag) {
if (flag) {
if (id == 0) {
mLeftButton.setVisibility(View.VISIBLE);
} else {
mRightButton.setVisibility(View.VISIBLE);
}
} else {
if (id == 0) {
mLeftButton.setVisibility(View.GONE);
} else {
mRightButton.setVisibility(View.GONE);
}
}
}

/**
* 暴露一个方法给调用者来注册接口回调,通过接口来获得回调者对接口方法的实现
*
* @param mListener
*/
public void setOntopbarClickListener(topbarClickListener mListener) {
this.mListener = mListener;
}

/**
* 接口对象,实现回调机制,在回调方法中通过映射的接口对象调用接口中的
* 方法,而不用考虑具体实现,具体实现由回调者自己去实现
*/
public interface topbarClickListener {
// 左按钮点击事件
void leftClick();
// 右按钮点击事件
void rightClick();
}
}


值得注意的是,在暴露接口时我们只把接口中未实现的方法(按钮点击事件)的具体实现逻辑交给实现类,也就是自定义控件的调用者去做就好。接口方法的回调依然由接口的映射对象在
TopBar
类中调用。而接口的映射对象只考虑调用接口中的方法而不用去考虑如何实现,使得我们的自定义控件本身和它的调用者之间完全解耦,其实这也是作为一个控件模板最基本的要求。那么分开来看一下代码是怎样实现这个思路的。

TopBar
类中定义公有抽象接口,代码如下:

/**
* 接口对象,实现回调机制,通过映射的接口对象调用接口中的
* 方法,而不用考虑具体实现,具体实现由回调者自己去实现
*/
public interface topbarClickListener {
// 左按钮点击事件
void leftClick();
// 右按钮点击事件
void rightClick();
}


然后暴露一个方法给调用者来实现接口的抽象方法,也就是按钮的点击监听事件的具体处理逻辑。代码如下:

/**
* 暴露一个方法给调用者来实现接口的抽象方法,也就是按钮的点击监听
*
* @param mListener
*/
public void setOntopbarClickListener(topbarClickListener mListener) {
this.mListener = mListener;
}


调用者在具体的实现类中就是通过
mTopbar = (TopBar) findViewById(R.id.topBar)
得到
TopBar
控件的实例,然后由
TopBar
控件的实例调用上面的
setOntopbarClickListener(topbarClickListener mListener)
方法实现接口抽象方法的具体处理逻辑的。我们来看一下实现类的代码如下:

// 获得我们创建的TopBar对象
mTopbar = (TopBar) findViewById(R.id.topBar);

mTopbar.setOntopbarClickListener(new TopBar.topbarClickListener() {
@Override
public void leftClick() {
Toast.makeText(TopbarTestActivity.this, "left", Toast.LENGTH_SHORT).show();
}
@Override
public void rightClick() {
Toast.makeText(TopbarTestActivity.this, "right", Toast.LENGTH_SHORT).show();
}
});


然后接口方法的回调依然由接口的映射对象在
TopBar
类中调用。具体到我们的自定义控件,回调接口方法的接口映射对象就是
mLeftButton
mRightButton
这两个按钮。回调的代码在
TopBar
类的构造方法中的最后。代码如下:

// 按钮的点击事件,不需要具体的实现,只需调用接口的方法,回调时再具体去实现
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.leftClick();
}
});
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.rightClick();
}
});


另外,我们还在
TopBar
类中封装了设置按钮显示与否的方法
setButtonVisable(int id, boolean flag)
。代码如下:

/**
* 设置按钮的显示与否,通过id区分按钮,flag区分是否显示
*
* @param id
* @param flag
*/
public void setButtonVisable(int id, boolean flag) {
if (flag) {
if (id == 0) {
mLeftButton.setVisibility(View.VISIBLE);
} else {
mRightButton.setVisibility(View.VISIBLE);
}
} else {
if (id == 0) {
mLeftButton.setVisibility(View.GONE);
} else {
mRightButton.setVisibility(View.GONE);
}
}
}


setButtonVisable(int id, boolean flag)
方法还是由控件调用者在具体的实现类中通过控件的实例去调用。调用的代码如下:

// 设置按钮的显示状态
mTopbar.setButtonVisable(0, true);
mTopbar.setButtonVisable(1, true);


到这里自定义控件的封装可以说已经完成了。接下来就是使用我们自定义的控件
TopBar
了,是不是有点小激动呢?你只要把它当做一个普通的控件去使用就好了,就和平时使用
Button
TextView
ListView
这些控件一样去使用就好。区别有以下两点:

需要为自定义控件创建命名空间;

在声明自定义控件时需要制定完整的包名全路径;

为自定义孔家创建命名空间很简单,代码如下:

xmlns:custom="http://schemas.android.com/apk/res-auto"


其中xmlns就是在指定命名空间,这里的命名空间指定为custom。其他部分格式都是固定不变的,
http://schemas.android.com/apk/res-auto
这么地址就便是自定义控件。所以当调用者引入第三方控件,也就是自定义控件,的时候是通过命名空间来引用的。最后也是最重要的就是怎样使用自定义控件了。使用方法和系统控件一样,只是要求我们在标签上使用控件的完整包名全路径名即可,是不是很简单呢?代码如下:

    <?xml version="1.0" encoding="utf-8"?>
<com.wondertwo.app.application.TopBar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"android:id="@+id/topBar"
android:layout_width="match_parent"
android:layout_height="40dp"
custom:leftBackground="@drawable/blue_button"
custom:leftText="Back"
custom:leftTextColor="#FFFFFF"
custom:rightBackground="@drawable/blue_button"
custom:rightText="More"
custom:rightTextColor="#FFFFFF"
custom:title="自定义标题"
custom:titleTextColor="#123412"
custom:titleTextSize="15sp">

</com.wondertwo.app.application.TopBar>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: