自定义控件我有跟着几个大神做的成果做一些,但是一直没有仔细的完整的学一遍理论,所以这章要好好学一下。
这一章的内容如下:
——Android控件架构
——View的测量与绘制
——ViewGroup的测量与绘制
——自定义控件的三种方式
——事件的拦截机制
一、Android控件架构
如下图展示了View视图树,在控制树的顶部是一个ViewParent对象,是整个树的控制核心,所有交互管理事件都由它来统一调度和分配。ViewGroup控件作为父控件可以包含多个View控件,并管理其包含的View控件。通常在Activity中使用的findViewById()方法,就是在控制树中以树的深度优先遍历来查找对应元素。
每个Activity都包含一个Window对象,Android中通常用PhoneWindow来实现。PhoneWindow将一个DecorView设置为整个应用窗口的根View,它封装了一些窗口操作的通用方法,在显示上,它将屏幕分为两部分:TitleView和ContentView。如下图:
setContentView()即加载ContentView部分的布局。如果用户通过设置requestWindowFeature(Window.FEATURE_NO_TITLE)来设置全屏显示,就一定要放在setContentView()方法之前才能生效。
在代码中,当程序在onCreate()方法中调用setContentView()方法后,ActivityManagerService会回调onResume()方法,此时系统才会把整个DecorView添加到PhoneWindow中,并让其显示出来,从而最终完成界面绘制。
二、View的测量
Android系统提供了一个强大的类——MeasureSpec类来帮助我们测量View。它是一个32位的int值,高2位为测量的模式,低30位为测量的大小。测量的模式可以为以下三种
EXACTLY——精确值模式:当控件的宽高属性指定为具体数值时使用
AT_MOST——最大值模式:当控件的宽高属性指定为自适应时使用
UNSPECIFIED——不精确模式:它不指定其大小测量模式,通常在绘制自定义View时才使用。
在自定义控件时,若指定了控件具体宽高值或者是match_parent属性,则使用EXACTLY模式,使用指定的specSize即可;如果指定的是wrap_content属性,即AT_MOST模式,必须重写onMeasure()方法来指定大小,通常作法是取出我们指定的大小与specSize中最小的一个来作为最后的测量值。代码如下:
public class MainActivity extends View {
public MainActivity(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = 200;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,result);
}
}
return result;
}
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = 200;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,result);
}
}
return result;
}
}
三、View的绘制
测量好一个View后,通常需要通过继承View并重写它的onDraw()方法来完成绘图。onDraw()中有一个参数,就是Canvas对象,这个对象就像创建了一个画板,使用Paint就可以在上面作画了。创建Cavas对象代码:
Canvas canvas = new Canvas(bitmap);
传进去的bitmap与Canvas画布紧紧联系,这个过程称为装载画布,这个bitmap用来存储绘制在Canvas上的像素信息,调用所有的Canvas.drawXXX方法都发生在这个bitmap上。
canvas.drawBitmap(bitmap1,0,0,null);
//绘制直线
canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);
//绘制矩形
canvas.drawRect(float left, float top, float right, float bottom, Paint paint);
//绘制圆形
canvas.drawCircle(float cx, float cy, float radius, Paint paint);
//绘制字符
canvas.drawText(String text, float x, float y, Paint paint);
//绘制图形
canvas.drawBirmap(Bitmap bitmap, float left, float top, Paint paint);
四、ViewGroup的测量与绘制
ViewGroup会管理其子View,其中一个管理项目就是负责子View的显示大小。当ViewGroup指定为wrap_content时,ViewGroup会对子View遍历,从子View的大小来确定自己的大小。如果ViewGroup有指定的值则按值设定自己的大小。
子View测量完后,ViewGroup会遍历子View的Layout方法来指定其具体显示的位置,从而决定其布局位置。
自定义ViewGroup时,通常会重写onLayout()方法来控制其子View显示位置的逻辑。
ViewGroup通常不需绘制。但是它会使用dispatchDraw()方法来绘制其子View,其过程是通过遍历所以子View,并调用子View的绘制方法来完成绘制工作。
五、自定义View
心心念念想学的一部分。。它的作用就不用多说了,直接快点开始学把~
在View中比较重要的回调方法
1、onFinishInflate()
//从XML加载组件后回调
@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
}
2、onSizeChanged()
//组件大小改变时回调
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// TODO Auto-generated method stub
super.onSizeChanged(w, h, oldw, oldh);
}
3、onMeasure()
// 回调该方法进行测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
4、onLayout()
// 回调该方法来确定显示的位置
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO Auto-generated method stub
super.onLayout(changed, left, top, right, bottom);
}
5、onTouchEvent()
// 监听到触摸时间时回调
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return super.onTouchEvent(event);
}
6、onDraw()
// 绘图
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
}
创建自定义View的时候,并不需要重写所有的方法。通常有一下三种方法来实现自定义的控件:
对现有控件进行拓展
通过组合来实现新的控件
重写View来实现全新的控件
1、对现有控件进行拓展
这种方法就是在原生控件的基础上进行拓展,增加新的功能、修改显示的UI等。
第一个例子:让TextView的背景更加丰富
TextView使用onDraw()方法绘制要显示的文字,继承了系统的TextView后,一定要重写其onDraw()方法才能修改TextView展示的 效果。其实程序调用super.onDraw()方法来实现原生控件的功能,但是在调用super.onDraw()方法前和之后,我们可以实现自己的逻辑,即在系统绘制文字前后,完成自己的操作。如下所示:
@Override
protected void onDraw(Canvas canvas) {
//在回调父类之前,实现自己的逻辑,对textview来说就是绘制文本内容前
super.onDraw(canvas);
//在回调父类之后,实现自己的逻辑,对textview来说就是绘制文本内容后
}
然后我们分析要实现这样的效果,需要绘制两个不同大小的矩形,形成一个重叠效果。那我们就要先初始化两个画笔:
//实例化画笔1
paint1 = new Paint();
//设置颜色
paint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
//设置style
paint1.setStyle(Paint.Style.FILL);
//实例化另一个画笔
paint2 = new Paint();
paint2.setColor(Color.YELLOW);
paint2.setStyle(Paint.Style.FILL);
然后开始绘制
//绘制外层
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint1);
//绘制内层
canvas.drawRect(10, 10, getMeasuredWidth() - 10, getMeasuredHeight() - 10, paint2);
canvas.save();
//绘制文字前平移10像素
canvas.translate(10, 0);
//父类完成方法
super.onDraw(canvas);
canvas.restore();
第二个例子:实现一个动态的文字闪动效果
想要实现这一效果,可以充分利用Android中Paint对象的Shader渲染器。通过设置一个不断变化的LinearGradient,并使用带有该属性的Paint对象来绘制要显示的文字。首先我们要在onSizeChanged()方法中完成一些初始化操作。
@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);
//矩阵
matrix = new Matrix();
}
}
}
其中最关键的就是使用getPaint()方法获取当前绘制TextView的Paint对象,并给这个Paint对象设置原声TextView没有的LinearGradient属性。最后在onDraw()方法中,通过矩阵的方法来不断平移渐变效果,从而产生动态闪动效果:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (matrix != null) {
mTranslate += mViewWidth + 5;
if (mTranslate > 2 * mViewWidth / 5) {
mTranslate = -mViewWidth;
}
matrix.setTranslate(mTranslate, 0);
mLinearGradient.setLocalMatrix(matrix);
//每隔100毫秒闪动一下
postInvalidateDelayed(100);
}
}
}
下面放上完整的效果和代码:
布局代码
<?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="match_parent"
android:orientation="vertical">
<com.example.administrator.myview.TextViewTestOne
android:id="@+id/tv_01"
android:layout_width="match_parent"
android:layout_height="80dp"
android:text="Android"
android:textSize="25sp"/>
<com.example.administrator.myview.TextViewTestTwo
android:id="@+id/tv_02"
android:layout_marginTop="30dp"
android:layout_width="match_parent"
android:layout_height="80dp"
android:text="Android"
android:textSize="25sp"/>
TextViewTestOne.java
public class TextViewTestOne extends TextView {
private Paint paint1, paint2;
public TextViewTestOne(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
//实例化画笔1
paint1 = new Paint();
//设置颜色
paint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
//设置style
paint1.setStyle(Paint.Style.FILL);
//同上
paint2 = new Paint();
paint2.setColor(Color.YELLOW);
paint2.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
//绘制外层
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint1);
//绘制内层
canvas.drawRect(10, 10, getMeasuredWidth() - 10, getMeasuredHeight() - 10, paint2);
canvas.save();
//绘制文字前平移10像素
canvas.translate(10, 0);
//父类完成方法
super.onDraw(canvas);
canvas.restore();
}
}
TextViewTestTwo.java
public class TextViewTestTwo extends TextView {
private LinearGradient mLinearGradient;
private Matrix mGradientMatrix;
private Paint mPaint;
int mTranslate;
int mViewWidth;
public TextViewTestTwo(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@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,0xffffff,Color.BLUE},null,Shader.TileMode.CLAMP
);
mPaint.setShader(mLinearGradient);
mGradientMatrix = new Matrix();
}
}
}
@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);
}
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextViewTestOne tv_01 = (TextViewTestOne) findViewById(R.id.tv_01);
TextViewTestTwo tv_02 = (TextViewTestTwo) findViewById(R.id.tv_02);
}
}
2、创建复合控件
这种方式通常需要继承一个合适的ViewGroup,再给它添加指定功能的控件,从而组合成新的复合控件。通过这种方式创建的控件,我们一般会给他指定一些可配置的属性,使其具有更强的拓展性。以下是一个TopBar示例。
1)定义属性
在res资源目录下创建一个attrs.xml的属性定义文件,然后通过如下代码定义相应的属性即可。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<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="reference|color" />
<attr name="rightText" format="string" />
</declare-styleable>
</resources>
确定号属性后,就可以创建一个自定义控件TopBar,并让他继承ViewGroup,为了简单这里继承了RelativeLayout。在构造方法中通过如下代码来获取在xml布局文件中自定义的属性:
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
系统提供了TypedArray这样的数据机构来获取自定义属性集,通过它的对象的getString(),getColor()等方法,就可以获取这些定义的属性值,代码如下:
//通过这个方法,你可以从你的attrs.xml文件下读取读取到的值存储在你的TypedArray
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
//读取出相应的值设置属性
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);
mRightBackgroup = ta.getDrawable(R.styleable.TopBar_rightBackground);
mRightText = ta.getString(R.styleable.TopBar_rightText);
mTitleSize = ta.getDimension(R.styleable.TopBar_titleTextSize, 10);
mTitleColor = ta.getColor(R.styleable.TopBar_titleTextColor, 0);
mTitle = ta.getString(R.styleable.TopBar_title);
//获取完TypedArray的值之后,一般要调用recycle方法来避免重复创建时候的错误
ta.recycle();
2)组合控件
接下来,就要开始组合基础控件。创建左边的按钮,中间的标题栏和右边的按钮,设置他们的一些属性,然后设置各个控件在ViewGroup中的布局,最后用addView()方法将这三个控件加入到定义的TopBar模板中。
mLeftButton = new Button(context);
mRightButton = new Button(context);
mTitleView = new TextView(context);
//为创建的元素赋值
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText);
mRightButton.setTextColor(mRightTextColor);
mRightButton.setBackground(mRightBackgroup);
mRightButton.setText(mRightText);
mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleColor);
mTitleView.setTextSize(mTitleSize);
mTitleView.setGravity(Gravity.CENTER);
//为组件元素设置相应的布局元素
mLeftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);
//添加到ViewGroup
addView(mLeftButton,mLeftParams);
mRightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);
addView(mRightButton,mRightParams);
mTitleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);
addView(mTitleView,mTitleParams);
定义接口——接下来创建一个接口,实现按钮的点击回调
//定义接口
//接口对象,实现回调机制,在回调方法中
//通过映射的接口对象调用接口中的方法
//而不用去考虑如何实现,具体的实现由调用者去创建
public interface topbarClickListener {
//左按钮点击事件
void leftClick();
//右按钮点击事件
void rightClick();
}
暴露接口给调用者——在模板方法中,为左右按钮增加点击事件
//按钮的点击事件,不需要具体的实现
//只需调用接口的方法,回调的时候,会有具体的实现
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mListener.leftClick();
}
});
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mListener.rightClick();
}
});
//暴露一个方法给调用者来注册接口回调
//通过接口来获得回调者对接口方法的实现
public void setOnTopbarClickListener(topbarClickListener mListener){
this.mListener = mListener;
}
实现接口回调——在调用这的代码中,要实现这样一个接口,并完成接口中的方法
mTopbar = (TopBar) findViewById(R.id.mTopbar);
mTopbar.setOnTopbarClickListener(new topbarClickListener() {
@Override
public void leftClick() {
Toast.makeText(MainActivity.this, "点击左侧按钮", Toast.LENGTH_SHORT).show();
}
@Override
public void rightClick() {
Toast.makeText(MainActivity.this, "点击右侧按钮", Toast.LENGTH_SHORT).show();
}
});
为了简单,只显示了两个Toast来区分不同按钮的点击事件
3)引用UI模板
最后一步,自然是在需要使用的地方引用UI模板,在引用前,需要指定引用第三方控件的名字空间。
例如
xmlns:android="http://schemas.android.com/apk/res/android"
这行代码就是在指定引用的名字空间xmlns,即xml namespace。这里指定了名字空间为“android”,因此在接下来使用系统属性时,才可以使用“android:”来引用Android的系统属性。
如果使用自定义属性,就要创建自己的名字空间:
<?xml version="1.0" encoding="utf-8"?>
<com.example.administrator.topbartest.TopBar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="40dp"
custom:leftBackground="#128"
custom:leftText="Back"
custom:leftTextColor="#FFFFFF"
custom:rightBackground="#128"
custom:rightText="More"
custom:rightTextColor="#FFFFFF"
custom:title="自定义标题"
custom:titleTextColor="#000"
custom:titleTextSize="15sp">
</com.example.administrator.topbartest.TopBar>
通过如上所示代码,我们就可以在其他的布局中,直接通过标签来引用这个UI模板View。
下面放上完整的代码
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--声明使用自定义属性,通过name属性来确定引用的名字-->
<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="reference|color" />
<attr name="rightText" format="string" />
</declare-styleable>
</resources>
topbarClickListener.java
//定义接口
//接口对象,实现回调机制,在回调方法中
//通过映射的接口对象调用接口中的方法
//而不用去考虑如何实现,具体的实现由调用者去创建
public interface topbarClickListener {
//左按钮点击事件
void leftClick();
//右按钮点击事件
void rightClick();
}
TopBar.java
public class TopBar extends RelativeLayout {
private int mLeftTextColor;
private Drawable mLeftBackground;
private String mLeftText;
private int mRightTextColor;
private Drawable mRightBackgroup;
private String mRightText;
private float mTitleSize;
private int mTitleColor;
private String mTitle;
private Button mLeftButton,mRightButton;
private TextView mTitleView;
private LayoutParams mLeftParams,mRightParams,mTitleParams;
private topbarClickListener mListener;
//带参构造方法
public TopBar(Context context, AttributeSet attrs) {
super(context, attrs);
//通过这个方法,你可以从你的attrs.xml文件下读取读取到的值存储在你的TypedArray
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
//读取出相应的值设置属性
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);
mRightBackgroup = ta.getDrawable(R.styleable.TopBar_rightBackground);
mRightText = ta.getString(R.styleable.TopBar_rightText);
mTitleSize = ta.getDimension(R.styleable.TopBar_titleTextSize, 10);
mTitleColor = ta.getColor(R.styleable.TopBar_titleTextColor, 0);
mTitle = ta.getString(R.styleable.TopBar_title);
//获取完TypedArray的值之后,一般要调用recycle方法来避免重复创建时候的错误
ta.recycle();
mLeftButton = new Button(context);
//按钮的点击事件,不需要具体的实现
//只需调用接口的方法,回调的时候,会有具体的实现
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mListener.leftClick();
}
});
mRightButton = new Button(context);
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mListener.rightClick();
}
});
mTitleView = new TextView(context);
//为创建的元素赋值
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText);
mRightButton.setTextColor(mRightTextColor);
mRightButton.setBackground(mRightBackgroup);
mRightButton.setText(mRightText);
mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleColor);
mTitleView.setTextSize(mTitleSize);
mTitleView.setGravity(Gravity.CENTER);
//为组件元素设置相应的布局元素
mLeftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);
//添加到ViewGroup
addView(mLeftButton,mLeftParams);
mRightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);
addView(mRightButton,mRightParams);
mTitleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);
addView(mTitleView,mTitleParams);
}
//暴露一个方法给调用者来注册接口回调
//通过接口来获得回调者对接口方法的实现
public void setOnTopbarClickListener(topbarClickListener mListener){
this.mListener = mListener;
}
}
topbar.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.administrator.topbartest.TopBar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="40dp"
custom:leftBackground="#128"
custom:leftText="Back"
custom:leftTextColor="#FFFFFF"
custom:rightBackground="#128"
custom:rightText="More"
custom:rightTextColor="#FFFFFF"
custom:title="自定义标题"
custom:titleTextColor="#000"
custom:titleTextSize="15sp">
</com.example.administrator.topbartest.TopBar>
activity_main.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="match_parent">
<include
layout="@layout/topbar"
android:id="@+id/mTopbar"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TopBar mTopbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTopbar = (TopBar) findViewById(R.id.mTopbar);
mTopbar.setOnTopbarClickListener(new topbarClickListener() {
@Override
public void leftClick() {
Toast.makeText(MainActivity.this, "点击左侧按钮", Toast.LENGTH_SHORT).show();
}
@Override
public void rightClick() {
Toast.makeText(MainActivity.this, "点击右侧按钮", Toast.LENGTH_SHORT).show();
}
});
}
}
3、重写View来实现全新的控件
例子一:比例图
我们来分析一下这个图是怎么画出来的。首先这个自定义View可以分为三部分,分别是中间的圆形,中间显示的文字和外圈的弧线。
下面直接看完整代码:
CircleView.java
public class CircleView extends View {
//圆的长度
private int mCircleXY;
//屏幕高宽
private int w,h;
//圆的半径
private float mRadius;
//圆的画笔
private Paint mCirclePaint;
//弧线的画笔
private Paint mArcPaint;
//文本画笔
private Paint mTextPaint;
//需要显示的文字
private String mShowText = "hahahaha";
//文字大小
private int mTextSize = 50;
//圆心扫描的弧度
private int mSweepAngle = 270;
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//获取屏幕高宽
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
w = wm.getDefaultDisplay().getWidth();
h = wm.getDefaultDisplay().getHeight();
init();
}
private void init() {
mCircleXY = w/2;
mRadius = (float)(w * 0.5 / 2);
mCirclePaint = new Paint();
mCirclePaint.setColor(Color.BLUE);
mArcPaint = new Paint();
//设置线宽
mArcPaint.setStrokeWidth(100);
//设置空心
mArcPaint.setStyle(Paint.Style.STROKE);
//设置颜色
mArcPaint.setColor(Color.BLUE);
mTextPaint = new Paint();
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTextSize(mTextSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制矩形
RectF mArcRectF = new RectF((float)(w * 0.1),(float)(w * 0.1),(float)(w * 0.9),(float)(w * 0.9));
//绘制圆
canvas.drawCircle(mCircleXY,mCircleXY,mRadius,mCirclePaint);
//绘制弧线
canvas.drawArc(mArcRectF,270,mSweepAngle,false,mArcPaint);
//绘制文本
canvas.drawText(mShowText,0,mShowText.length(),mCircleXY-mTextSize * 2 ,mCircleXY+mTextSize/2,mTextPaint);
}
//设置一个对外的弧度方法
public void setSweepValues(int sweepValues){
if(sweepValues != 0){
mSweepAngle = sweepValues;
}else {
//如果没有,我们默认设置
mSweepAngle = 30;
}
//刷新
invalidate();
}
}
activity_main.xml
<?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">
<com.example.administrator.circledemo.CircleView
android:id="@+id/cv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CircleView circleView = (CircleView) findViewById(R.id.cv);
circleView.setSweepValues(180);
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CircleView circleView = (CircleView) findViewById(R.id.cv);
circleView.setSweepValues(180);
}
}
例子二、音频条形图
直接看代码吧
YinPinRectView.java
public class YinPinRectView extends View {
private Paint mPaint;
//定义矩形数量
int mRectCount = 20;
//定义矩形宽度和高度
private int mRectWidth ,mRectHeight;
//设置偏移量
private int offset = 5;
//定义当前矩形高度
private float currentHeight;
//设置一个随机数
private double mRandom;
private LinearGradient mLinearGradient;
//屏幕宽高
private int w;
public YinPinRectView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
w = wm.getDefaultDisplay().getWidth();
init();
}
private void init() {
mPaint = new Paint();
//设置线宽
mPaint.setStrokeWidth(30);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(int i = 0;i<mRectCount;i++){
currentHeight = getRectHeight();
canvas.drawRect((float)(w * 0.05 + mRectWidth * i + offset),currentHeight,
(float)(w * 0.05 + mRectWidth * (i+1) ),mRectHeight,mPaint);
}
postInvalidateDelayed(300);
}
//随即改变矩形的高
private float getRectHeight() {
mRandom = Math.random();
float currentHeight = (float) (mRectHeight * mRandom);
return currentHeight;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
w = getWidth();
mRectHeight = getHeight();
mRectWidth = (int)(w /mRectCount);//计算矩形的宽
//渐变效果
mLinearGradient = new LinearGradient(0,0,mRectWidth,mRectCount,Color.YELLOW,Color.BLUE, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
}
}
activity_main.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="match_parent">
<com.example.administrator.yinpinrectdemo.YinPinRectView
android:id="@+id/yp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
YinPinRectView yp = (YinPinRectView) findViewById(R.id.yp);
}
}