整理昨日在群英传上深入学习到的自定义view的知识
一,对现有控件进行扩展:
在原生控件的基础上进行拓展,增加新的功能、修改显示的UI等,一般来说,我们可以在onDraw()方法上对原生控件行为进行拓展
以TextView的背景更加丰富,让Textview的背景更加丰富,给其多绘制几层背景。
1.1
原生的TextView使用onDraw()方法绘制要显示的文字。当继承了TextView之后如果不重写该方法,则不会修改Textview的任何效果
@Override protected void onDraw(Canvas canvas) { //在回调父类方法前,实现自己的逻辑,对TextView来说即是在绘制文本前 super.onDraw(canvas); //在回调父类方法后,实现自己的逻辑,对TextView来说即是在绘制文本后 }
通过以上思路,就可以实现自定义TextView了。我们在构造方法中完成必要对象的初始化的工作。
例如:初始化两个画笔paint;
mPaint1= new Paint(); mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light)); mPaint1.setStyle(Paint.Style.FILL); mPaint2= new Paint(); mPaint2.setColor(Color.YELLOW); mPaint2.setStyle(Paint.Style.FILL);
然后在绘制文字前,我们可以绘制两个不同大小的矩形,形成一个重叠效果,再让系统调用 super.onDraw(canvas)方法,执行绘制文字的工作。
//绘制外层矩形 canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint1); //绘制内层矩形 canvas.drawRect(10,10,getMeasuredWidth()-10,getMeasuredHeight()-10,mPaint1); canvas.save(); //绘制文字前平移10像素 canvas.translate(10,0);
super.onDraw(canvas);
canvas.restore();
1.2实现稍微复杂的TextView,实现动态文字的闪动效果。
实现这个效果可以使用Paint对象的Shader渲染器。通过设置一个不断变化的LinearGradient,并使用带有该属性的Paint对象来绘制要显示的文字。首先要在onSizeChanged()方法中进行一些对象的初始化工作,并根据view的宽度设置一个LinearGradient
渐变渲染器,代码如下:
mViewWidth为view的宽度,通过getMeasure方法可以获取
mPaint,这里最关键的一个地方,通过getPaint()方法获取当前绘制TextView的Paint对象
mLinearGradient线性渐变属性,LinearGradient是基于Shader渲染器的
@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){ //获取当前绘制TextView的Paint对象 mPaint=getPaint(); //LinearGradient是基于Shader渲染器的 mLinearGradient= new LinearGradient( 0, 0, mViewWidth, 0, new int[]{ Color.BLUE,0xffffffff, Color.BLUE }, null, Shader.TileMode.CLAMP);
//给paint设置原生TextView没有的LinearGradient的属性 mPaint.setShader(mLinearGradient); //在这里初始化好Matrix的对象,用于接下来实现平移效果,当然设置matrix还可以实现其他效果,例如旋转,缩放等
mGradientMatrix= new Matrix(); } } }
最后,在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为例
2.1定义属性:
为一个View提供可定义的属性非常简单,只需要在res资源目录的values目录下创建一个attrs.xml的文件。
name为属性的名称,format属性来指定属性的类型
<?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>
系统提供了TypedArray这样的数据结构来获取自定义属性集,后面引用的styleable的TopBar,就是在xml中通过<delcare-styleable name="TopBar">所指定的name名。通过TypedArray的getString(),getColor()等方法,就可以获取这些定义的属性值。
当获取完属性后,需要调用TypedArray的recyle方法来完成资源的回收
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); 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();
2.2组合控件
//创建组件 mLeftButton= new Button(context); mRightButton= new Button(context); mTitleView = new TextView(context); //为创建的组件元素赋值,值就来源于我们在引用的xml文件中给对应属性的属性值 mLeftButton.setText(mLeftText); mLeftButton.setTextColor(mLeftTextColor); mLeftButton.setBackground(mLeftBackGround); mRightButton.setText(mRightText); mRightButton.setTextColor(mRightTextColor); mRightButton.setBackground(mRightBackGround); 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); addView(mLeftButton,mLeftParams); mRightParams= new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE); addView(mRightButton,mRightParams); mTitleParams= new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE); addView(mTitleView,mTitleParams);
要给左右按钮设计点击事件。每个调用者所需要这些按钮能够实现的功能都是不一样的,因此不能直接在UI模板中添加具体的实现逻辑
定义接口
public interface topbarClickListener{ //左边按钮点击事件 void leftClick(); //右边按钮点击事件 void rightClick(); }
对两个按钮设置监听事件
mRightButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mListener!=null) //此处要判监听是否为空,否则在没有调用接口的情况下会报空指针异常 mListener.rightClick(); } });
mLeftButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mListener!=null) //此处要判监听是否为空,否则在没有调用接口的情况下会报空指针异常
mListener.leftClick(); } });
暴露接口给调用者
//暴露一个方法给调用者来注册接口调用,通过接口获得回调者对接口方法的实现 public void setOnTopbarClickListener(topbarClickListener listener){ this.mListener=listener; }
这样TopBar的基本功能就实现了,
当然还可以提供其他的接口方法来设置TopBar的属性
例如:提供setButtonVisiable方法来设置button是否显示
public void setButtonVisable(int id,boolean flag){ if (flag){ if (id==0){ mLeftButton.setVisibility(VISIBLE); }else{ mRightButton.setVisibility(VISIBLE); } }else{ if (id==0){ mLeftButton.setVisibility(GONE); }else{ mRightButton.setVisibility(GONE); } } }
2.3如何引用自定义的UI模板
第三方控件使用如下代码来引入名字空间。
xmlns:custom="http://schemas.android.com/apk/res-auto"
然后在xml文件中就可以使用自定义的属性了,使用custom来引用。我们进一步将这个UI模板写到一个布局文件中。
<com.example.TopBar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
custom:leftBackGround="@color/blue"
custom:leftText="Back" custom:leftTextColor="@color/yellow" custom:rightBackGround="@color/yellow" custom:rightText="More" custom:rightTextColor="@color/blue" custom:title="自定义标题" custom:titleTextColor="@color/black" custom:titleTextSize="10sp" android:layout_width="match_parent" android:layout_height="40dp" > </com.example.TopBar>
那么在其他布局文件中就可以通过<include>标签进行引用了
如下所示
<include layout="@layout/layout"/>