自定义控件,1,自定义属性;2,创建复合控件

时间:2021-12-30 20:38:22

整理昨日在群英传上深入学习到的自定义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);

        }

    }

最后效果是这样的:

自定义控件,1,自定义属性;2,创建复合控件

二、创建复合控件

创建复合控件可以很好的创建出具有重用功能的控件集合,这种方式通常需要继承一个合适的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"/>