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

来源:互联网 发布:亚历克斯·张伯伦 知乎 编辑:程序博客网 时间:2024/06/07 11:16

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

@Overrideprotected 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
原创粉丝点击