Android群英传学习——第三章、Android控件架构与自定义控件详解

自定义控件我有跟着几个大神做的成果做一些,但是一直没有仔细的完整的学一遍理论,所以这章要好好学一下。
这一章的内容如下:
——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);
    }
}

五、事件拦截机制分析

http://blog.csdn.net/chunqiuwei/article/details/41084921

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值