Android控件架构与自定义控件

来源:互联网 发布:全民枪战刷龙软件 编辑:程序博客网 时间:2024/06/13 22:14

View 的测量

  @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }

如果查看
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
会发现,最终会调用setMeasuredDimension(width,height)这个方法,所以我们就可以直接传递width和height到这个方法中

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }

测量模式可以分为以下三种

  • EXACTLY 精确值模式
    对自定义view的width和height需指定具体的dip.
    android:layout_width="100dp"
  • AT_MOST 最大值模式
    控件的layout_width 和layout_height 属性制定为warp_content是,控件大小一般随着控件的子空间或者内容的变化而变化,只要不超过父控件允许的最大尺寸即可
  • UNSPECIFIED 无规定模式
    不指定测量大小,像多大多大.

view中重写onMeasure()方法

    自定义view重写`onMeasure()`,只支持 EXACTLY模式,所以如果在自定义控件的时候补充些onMeasu()方法的话,就只能使用EXACTLY模式.控件可以响应你制定的具体狂傲值或者是match_parent.而如果要自定义view支持warp_content属性,那么就必须重写onMeasure()方法来制定warp_conte时的大小

measureWidth()

private int measureWidth(int measureSpec) {        int result = 0;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        if (specMode == MeasureSpec.EXACTLY) {            result = specSize;        } else {            result = 200;            if (specMode == MeasureSpec.AT_MOST) {                result = Math.min(result, specSize);            }        }        return result;    }

measureHeight();

和measureWidth()差不多.

onMeasure();

@Override    protected void onMeasure(int widthMeasureSpec,                             int heightMeasureSpec) {         //这里直接调用setMeasuredDimension();传进去宽高.        setMeasuredDimension(                measureWidth(widthMeasureSpec),                measureHeight(heightMeasureSpec));    }
                            |                            |                            |                            |                            √

自定义VIew

自定义view分为三种情况

  • 对现有控件进行拓展
  • 通过组合来实现新的控件
  • 重写view来实现全新的控件

1.对现有控件进行拓展

TextView重写

重写TextView,进行拓展

    @Override    protected void onDraw(Canvas canvas) {        //在回调父类方法之前,实现自己的逻辑,对TextView来说即是在绘制文本内容前        super.onDraw(canvas);        //在回调父类方法之后,实现自己的逻辑,对TextView来说即是在绘制文本内容后    }
实例化自己的画笔
private void initView() {        //首先将画笔实例化        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);    }
重写onDraw(Canvas canvas)
@Override    protected void onDraw(Canvas canvas) {        // 绘制外层矩形        canvas.drawRect(                0,                0,                getMeasuredWidth(),                getMeasuredHeight(),                mPaint1);        // 绘制内层矩形,将  边距 调整为10 距离外围        canvas.drawRect(                10,                10,                getMeasuredWidth() - 10,                getMeasuredHeight() - 10,                mPaint2);        canvas.save();        // 绘制文字前平移10像素        canvas.translate(10, 0);        // 父类完成的方法,即绘制文本        super.onDraw(canvas);        canvas.restore();    }
完整代码
public class MyTextView extends TextView {    private Paint mPaint1, mPaint2;    public MyTextView(Context context) {        super(context);        initView();    }    public MyTextView(Context context, AttributeSet attrs) {        super(context, attrs);        initView();    }    public MyTextView(Context context, AttributeSet attrs,                      int defStyleAttr) {        super(context, attrs, defStyleAttr);        initView();    }    private void initView() {        //首先将画笔实例化        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);    }    @Override    protected void onDraw(Canvas canvas) {        // 绘制外层矩形        canvas.drawRect(                0,                0,                getMeasuredWidth(),                getMeasuredHeight(),                mPaint1);        // 绘制内层矩形,将  边距 调整为10 距离外围        canvas.drawRect(                10,                10,                getMeasuredWidth() - 10,                getMeasuredHeight() - 10,                mPaint2);        canvas.save();        // 绘制文字前平移10像素        canvas.translate(10, 0);        // 父类完成的方法,即绘制文本        super.onDraw(canvas);        canvas.restore();    }}

2.创建复合控件(TopBar)

这里写图片描述

原理

  1. 实现RelativeLayout
  2. 在初始化方法中,接收自定义属性的值
  3. 通过new 的方法来实现你需要的控件,例如 Button but = new Button(Context);
  4. 通过set******();将自定义属性传到空间当中
  5. 自定义控件的 布局属性 LayoutParams
  6. addView(View,LayoutParams);将控件以及布局属性,添加到主布局当中去.

2.1为自己的view自定义属性

<declare-styleable name="TopBar"><!--添加了9个属性,分别是左右Button和中间的Title的text,textColor,TextSize-->        <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>

2.2接受自定义的属性,

其中最重要的就是

TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.TopBar);

通过这个方法,将你在atts.xml中定义的declare-styleable 的所有属性的值存储到TypedArray中

         // 通过这个方法,将你在atts.xml中定义的declare-styleable        // 的所有属性的值存储到TypedArray中        TypedArray ta = context.obtainStyledAttributes(attrs,                R.styleable.TopBar);        // 从TypedArray中取出对应的值来为要设置的属性赋值        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.3 实例化控件,并将获取到的自定义属性,set到控件当中.

        mLeftButton = new Button(context);        mRightButton = new Button(context);        mTitleView = new TextView(context);        // 为创建的组件元素赋值        // 值就来源于我们在引用的xml文件中给对应属性的赋值        mLeftButton.setTextColor(mLeftTextColor);        mLeftButton.setBackground(mLeftBackground);        mLeftButton.setText(mLeftText);        mRightButton.setTextColor(mRightTextColor);        mRightButton.setBackground(mRightBackground);        mRightButton.setText(mRightText);        mTitleView.setText(mTitle);        mTitleView.setTextColor(mTitleTextColor);        mTitleView.setTextSize(mTitleTextSize);        mTitleView.setGravity(Gravity.CENTER);

2.4 addView(View,LayoutParams);

        // 为组件元素设置相应的布局元素        mLeftParams = new LayoutParams(                LayoutParams.WRAP_CONTENT,                LayoutParams.MATCH_PARENT);        mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);        // 添加到ViewGroup        addView(mLeftButton, mLeftParams);        mRightParams = new LayoutParams(                LayoutParams.WRAP_CONTENT,                LayoutParams.MATCH_PARENT);        mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);        addView(mRightButton, mRightParams);        mTitlepParams = new LayoutParams(                LayoutParams.WRAP_CONTENT,                LayoutParams.MATCH_PARENT);        mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);        addView(mTitleView, mTitlepParams);

到这整个布局都写完了.基本上是可以显示的没有问题,下面的话就是添加一个特有的属性,比如说,onClick();


2.4 对左右Button添加OnClick();

摘要
1. 定义Interface,添加回调方法
2. 类中添加 Interface属性,并设置气setter,方法,用于外部调用
3. 控件添加onclick()

2.4.1 定义Interface,添加回调方法
    // 接口对象,实现回调机制,在回调方法中    // 通过映射的接口对象调用接口中的方法    // 而不用去考虑如何实现,具体的实现由调用者去创建    public interface topbarClickListener {        // 左按钮点击事件        void leftClick();        // 右按钮点击事件        void rightClick();    }
2.4.2 类中添加 Interface属性,并设置气setter,方法,用于外部调用
    // 映射传入的接口对象    private topbarClickListener mListener;    // 暴露一个方法给调用者来注册接口回调    // 通过接口来获得回调者对接口方法的实现    public void setOnTopbarClickListener(topbarClickListener mListener)     {        this.mListener = mListener;    }
2.4.3 控件添加onclick()
        // 按钮的点击事件,不需要具体的实现,        // 只需调用接口的方法,回调的时候,会有具体的实现        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();            }        });

3.重写View实现全新的控件

这里的话就实现一个 音频条形图
这里写图片描述

首先来分析一下这个效果是如何实现的,之后在一步一步来实现

    既然是条形图,那就是一个个的矩形了.先画出多个矩形,两两之间的距离是5dip,也就是说,矩形的横坐标平移5个单位长度.  其中的颜色渐变用到了 Lineargradient extends Shard 是shard的子类.可以用paint.setShard();来设置渐变色.

1. 矩形的宽度是一定的,但是高度不同,所以需要生成一个随机数. Math.random();
2. 画矩形
3. 实现渐变色

onDraw();

@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //循环生成12个矩形,        for (int i = 0; i < mRectCount; i++) {            //生成0~1之间的随机数,之后 * 屏幕的高度,就是矩形的高度.小于屏幕的高度.            mRandom = Math.random();            float currentHeight = (float) (mRectHeight * mRandom);            //mWidth 获取到两侧 空余出来的空白的 坐标的宽度.            canvas.drawRect(                    (float) (mWidth * 0.4 / 2 + mRectWidth * i + offset),                    currentHeight,                    (float) (mWidth * 0.4 / 2 + mRectWidth * (i + 1)),                    mRectHeight,                    mPaint);        }        //300ms延迟之后重新 绘制新的图形        postInvalidateDelayed(300);    }

关于渐变色,

 @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mWidth = getWidth();        mRectHeight = getHeight();        //给两边各剩余 两份 的空间, 中间显示的宽度占6分,再分为12份        mRectWidth = (int) (mWidth * 0.6 / mRectCount);        mLinearGradient = new LinearGradient(                0,                0,                mRectWidth,                mRectHeight,                Color.YELLOW,                Color.BLUE,                Shader.TileMode.MIRROR);        mPaint.setShader(mLinearGradient);    }
LinearGradient extends Shard 的使用
Paint p=new Paint();LinearGradient lg=new LinearGradient(0,0,100,100,Color.RED,Color.BLUE,Shader.TileMode.MIRROR);  

参数一为渐变起初点坐标x位置,参数二为y轴位置,参数三和四分辨对应渐变终点,最后参数为平铺方式,这里设置为镜像

Gradient是基于Shader类,所以我们通过Paint的setShader方法来设置这个渐变,代码如下: mPaint.setShader(lg);
canvas.drawCircle(0,0,200,mPaint); //参数3为画圆的半径,类型为float型。

它除了定义开始颜色和结束颜色以外还可以定义,多种颜色组成的分段渐变效果

LinearGradient shader = new LinearGradient(0, 0, endX, endY, new int[]{startColor, midleColor, endColor},new float[]{0 , 0.5f, 1.0f}, TileMode.MIRROR);

其中参数new int[]{startColor, midleColor, endColor}是参与渐变效果的颜色集合,
其中参数new float[]{0 , 0.5f, 1.0f}是定义每个颜色处于的渐变相对位置,
这个参数可以为null,如果为null表示所有的颜色按顺序均匀的分布

http://blog.csdn.net/qaz13177_58_/article/details/7852389

先写到这了,后期慢慢来!

1 0