Android群英传笔记——第三章:Android控件架构与自定义控件讲解


真的很久没有更新博客了,三四天了吧,搬家干嘛的,心累,事件又很紧,抽时间把第三章大致的看完了,当然,我还是有一点View的基础的,可以先看下我之前写的几篇基础的View博客

Android绘图机制(一)——自定义View的基础属性和方法

Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解

Android绘图机制(三)——自定义View的三种实现方式以及实战项目操作

好了,不多说,我们直接来看书

一.Android控件架构

Android的每个控件都是占一块矩形的区域,大致的分两类,继承View和ViewGroup,ViewGroup相当于一个容器,他可以管理多个字View,,整个界面上的控件形成了一个树形结构,也就是我们常说的控件树,上层控件负责下层控件的测量和绘制,并且传递交互事件,通过findviewbyid()这个方法来获取,其实就是遍历查找,在树形图的顶部都有一个ViewParent对象,这就是控制核心,所有的交互管理事件都是由它统一调度和分配,从而进行整个视图的控制

通常情况下,我们要显示一个activity的视图,需要使用setContentView()方法,那么这个方法到底做了些什么尼?我们先来看看Android节目的架构图

我们可以看到,每个activity都有一个window对象,在Android中,window对象通常由一个phonewindow去实现的,phonewindow将一个DecorView设置为整个窗口的根View,DecorView作为窗口界面的顶层视图,封装了一些通用的方法,可以说,DecorView将要显示的内容都给了phonewindow,这里面所有的View监听,都是通过winsowmanagerService来接收的,通过相应的回调来OnClicListener,在显示上,他将屏幕分成了两部分,一个title一个content,看到这里,大家应该能看到一个熟悉的界面ContentView,它是一个ID为content分framelayout,activity_main.xml就是设置在这个framelayout里面,我们可以建立一个编著你的视图来看下;

我们的视图的第二城是一个LinearLayout,作为ViewGroup,这一句的结构会根据对应的参数设置不同的布局,如最常用的上面一个TitleBar,下面就是内容,而在代码中当程序onCreate()时,也就设置了layout,执行完后,activitymanagerservice会直接调用onResume,这个时候系统会把整个DecorView添加到phonewindow,然后显示出来完成最后的绘制.

二.View的测量

我们想要绘制一个View,首先还是得知道这个View的大小,系统是如何把他绘制出来的,在Android中,我们要想绘制一个View,就必须要知道这个View的大小,然后告诉系统,这个过程在onMeasure()中进行


    /**
     * 测量
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

Android给我们提供了一个设计短小精悍的类——MeasureSpec类,通过他来帮助我们测量View, MeasureSpec是一个32位的int值,其中高2位为测量模式,低30为测量的大小,在计算中使用位运算时为了提高并且优化效率

测量模式

  • EXACTLY

表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  • AT_MOST

表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  • UNSPECIFIED

表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

View默认的onMeasure只支持EXACTLY模式,所以如果在自定义控件的时候不重写这个方法的话,也就只能使用EXACTLY模式了,控件可以响应你制定的具体的宽高值或者match_parent属性,如果我们自定义View要让他支持 wrap_content,那就必须重写onMeasure指定wrap_content时的大小,

通过MeasureSpec这个类,我们就获取到了View的测量模式和绘制的大小,有了这些信息我们就可以控制View最后显示的大小了,接下来,我们可以看一个简单的小例子,我们重写onMeasure这个方法


    /**
     * 测量
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

win下按住Ctrl查看super.onMeasure()这个方法,可以发现,系统最终还是会调用setMeasuredDimension()这个方法将测量的宽高设置进去从而完成测量工作

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

通过源码我们知道,我们自定义的高宽去设置,下面我们通过这个例子,来讲一下自定义的测量值

第一步,我们从MeasureSpec类中提取出具体的测量模式和大小

 int specMode = MeasureSpec.getMode(measureSpec);
 int specSize = MeasureSpec.getSize(measureSpec);

然后我们根据模式给出不同的测量值,当specMode为EXACTLY时,直接使用指定的specSize,当为其他两种模式地时候,我们就需要一个默认的值了,特别是指定包裹内容的时候,即AT_MOST模式,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;
    }

宽高的设置方法是一样的,所以,当我们在布局中设置match_parent他就铺满容器了,要是设置wrap_content,就是包裹内容,如果不设置的话,那他就只有200的大小了

三.View的绘制

当我们用wrap_content方法测量完成之后,我们就该重写onDraw()方法来绘制了,这个应该大家都很熟悉吧,首先我们要知道2D绘制的一些相关API

Canvas

顾名思义,画布的意思,而onDraw()就一个参数,就是Canvas了,我们要在其他地方绘制的话,就需要new对象了

Canvas canvas = new Canvas(Bitmap);

//绘制直线
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);

这个Bitmap是和Canvas紧密联系的,这个过程我们称之为装载画布,这个bitmap用来储存画布的一些像素信息,而且我们可以用canvas.drawxxx()来绘制相关的基础图形,详情可以看下

Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解

四.ViewGroup的测量

前面也说过,ViewGroup是老大,他是用来管理View的,包括View的大小什么的,当我们的ViewGroup大小是包裹内容的时候,实际上ViewGroup会遍历所有的子View,来获取View的大小,从而决定自身的大小,而在其他模式下,会通过具体的值来自定自身的大小

ViewGroup遍历所有的View会调用所有的View的onMeasure()方法来获取测量结果,

当子View测量完毕之后,,就需要将子View放在合适的地方,这部分是由onLayout()来进行的,

在我们自定义ViewGroup的时候,一般都要重写onLayout()方法控制子View显示位置的逻辑,同样,如果需要wrap_content属性,那就必须重写onLayout()方法了,这点和View是相同的

五.ViewGroup的绘制

ViewGroup在一般情况下是不会绘制的,因为他本身没有需要绘制的东西,如果不是指定ViewGroup的背景颜色,他连onDraw()都不会调用,但是ViewGroup会使用dispatchDraw()来绘制其他子View,其过程同样是遍历所哟普的子View,并调用子View的绘制方法来完成绘制的

 @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
    }

六.自定义View

终于来到大家喜欢的章节了,自定义View一直是个难点,Android自带的控件很难满足我们的需求,所欲我们需要重写控件或者自定义一个View,但是一般强大的View,都还是存在少许的bug的,而且现在Android ROM的多样性,适配问题也越来越麻烦了,当然,自定义View你熟悉之后,可以了解系统绘制控件的原理,而且能让你的APP更加美观,强大

在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);
    }

上面的方法并不需要全部写出来,看个人需要,一般我们实现自定义控件有三种方法

  • 1.对现有的控件进行扩展
  • 2.通过组件来实现新的控件
  • 3.重写View来实现全新的控件

对现有的控件进行扩展

这是一个我们十分常用的一个方法,用来对现有的控件进行扩展,比如TextView需要渐变啊什么的,挺常用的,这里我们就来写一个小栗子,我們先來看下效果

我们来分析一下这个效果,其实就是两层的绘制,我们依然要使用onDraw(),程序super.onDraw(canvas);方法来实现原生控件的功能,但是在调用super.onDraw(canvas)之前和之后都可以实现自己的逻辑


    @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);

最重要的部分就是什么时候调用super了,然后我们开始绘制

         //绘制外层
        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();

绘制是不是挺简单的,没错,这个是很简单,我放上完整的代码

package com.lgl.viewdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * 自定义TextView
 * Created by lgl on 16/3/4.
 */
public class CosuTextView extends TextView {

    private Paint paint1, paint2;

    public CosuTextView(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();
    }
}

接下来我们来看一个难一点的,先来看看效果

这个可以利用LinearGradient,Shader,Matrix,来完成,来实现一个闪闪发光的闪动效果,我们充分的利用Shader渲染器,来设置一个不断变化的LinearGradient,首先我们要在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()方法中获取当前特效他view的paint对象,并且设置LinearGradient属性,最后用矩阵不断平移渐变效果,就实现了这个效果,完整代码

package com.lgl.viewdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * 文本渐变
 * Created by lgl on 16/3/4.
 */
public class CosuTwoTextView extends TextView {

    int mViewWidth = 0;
    private Paint mPaint;
    private LinearGradient mLinearGradient;
    private Matrix matrix;
    private int mTranslate;

    public CosuTwoTextView(Context context, 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, 0xffffffff, Color.BLUE},
                        null, Shader.TileMode.CLAMP);
                mPaint.setShader(mLinearGradient);
                //矩阵
                matrix = new Matrix();
            }
        }
    }

    @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);
        }
    }
}

复合控件

创建一个复核人控件可以很好的创建出具有重要功能的控件集合,这种方式经常需要继承一个合适的ViewGroup,再给他添加指定功能的控件,从而组成一个新的合适的控件,通过这种方式创建的控件,我们一般都会给他指定的一些属性,让他具有更强的扩展性,下面就以一个TopBar为例子,讲解如何创建复合控件

1.定义属性

我们需要给他定义一些属性,这样的话,我们需要在values下新建一个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>

我们在代码中是可以用< declare-styleable >标签去声明一些属性的,然后name相当于ID让我们的类可以找到,,确定好之后,我们新建一个类,就叫TopBarView

package com.lgl.viewdemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewGroup;

/**
 * TopBar
 * Created by lgl on 16/3/6.
 */
public class TopBarView extends ViewGroup {

    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;

    //带参构造方法
    public TopBarView(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的值之后,一般要调用recyle方法来避免重复创建时候的错误
        ta.recycle();
    }

实际上,这个模板也是由左右两边的两个Button和中间的TextView组成的,既然这样,我们就可以把这些属性加给他们了,这里就可以直接定义了,完整赋值:

         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);

后面的云云就都是看你自己怎么去运用了,我们要想使用他们的话,还必须加上自己的命名空间,在layout里面,根布局都是有一个Android自带的命名引用的

xmlns:android="http://schemas.android.com/apk/res/android"

而我们自定义属性的haunted,也是需要自己的命名空间

xmlns:custom="http://schemas.android.com/apk/res/android"

这样我们就可以使用了

    <com.lgl.viewdemo.CosuTwoTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        custom:leftText="测试标题"
        custom:leftBackground = "#222222"
        custom:leftTextSize="18sp"/>

这样以此类推,就是我们使用自定义属性的方法了

重写View来实现全新的控件

这就是猪脚了,当我们Android原生的控件不满足的话,我们可以继承原来的控件修改,也可以组合起来使用,更加可以继承View创建一个新的控件View

下面我们就来实现一个简单的View,我们做法很简单

CircleView

package com.lgl.circledemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;

/**
 * 半弧圆
 * Created by lgl on 16/3/7.
 */
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 = "刘桂林";
    //文字大小
    private int mTextSize = 50;
    //圆心扫描的弧度
    private int mSweepAngle = 270;

    public CircleView(Context context, 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, mCircleXY + (mTextSize / 4), mTextPaint);
    }
}

这样就简单的画出来了,当你脑袋中有构思了的话,剩下的也只是挨个计算坐标点的问题了

我们还可以设置一些其他的状态,设置他的弧度,我们写一个对外的方法

 public void setSweepValues(float sweepValues){
        if(sweepValues !=- 0){
            mSweepAngle = sweepValues;
        }else{
            //如果没有,我们默认设置
            mSweepAngle = 30;
        }
        //记得刷新哦
        invalidate();
    }

我们我们就可以在主Activity中使用了

CircleView cv = (CircleView) findViewById(R.id.cv);
        //半圆弧度
        cv.setSweepValues(180);

这里我就设置180的半圆,然后我们来看看效果

自定义View的练习

我们来做一个小练习,这里的话我们做一个最常见的动画,就是音频条

只是演示的话我们来弄些虚拟数据(和原文一样,没写Demo)


 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //遍历绘制矩形,留中间间隔
        for (int i = 0; i < mRectCount; i++) {
            //开始绘制
            canvas.drawRect((float) (mWidth * 0.4 / 2 + mRectCount * i + offset),
                    currentHeight, (float) (mWidth * 0.4 / 2 + mRectWidth * (i + 1)), nRectHeight, mPaint);

        }

    }

我们只要写个循环就能画出所有的条目了,currentHeight是每个小矩形的高,我们通过这个方法来获取随机数

        //获取随机数
        mRandom = Math.random();
        currentHeight = ((float) (nRectHeight * mRandom));

这样我们只要每次重绘就会波动就形成了一个动画效果

         //延迟300去刷新
        postInvalidateDelayed(300);

当然,我们还可以加一个渐变颜色


    //增加渐变颜色

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mWidth = getWidth();
        nRectHeight = getHeight();
        mRectWidth = (int)(mWidth*0.6/mRectCount);

        mLinearGradient = new LinearGradient(0,0,mRectWidth,nRectHeight,Color.YELLOW,Color.BLUE, Shader.TileMode.CLAMP);
        mPaint.setShader(mLinearGradient);
    }

这样我们就能看到一点效果了,还是那句话,当你心中有这个图的时候,剩下的也只是计算坐标罢了

七.自定义ViewGroup

这个管理子View的管理者,我们来定义一下,通常我们自定义ViewGroup是需要onMeasure()来测量的,然后重写onLayout()来确定位置,重写onTouchEvent()来相应事件,这里我们定义一个类似系统ScrollView的效果,首先,我们测量

//测量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //次数
        int count = getChildCount();
        for (int i = 0; i < count; ++i) {
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }

接下来我们要对子View的位置计算,让每一个View放置的时候都是全屏,这样我们就滑动,我们这样来设置ViewGroup的高度

//位置
    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        int childCount = getChildCount();
        //设置ViewGroup的高度
        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
        mlp.height = mScreenHeight * childCount;
        setLayoutParams(mlp);

        for (int j = 0; j < childCount; j++){
            View child = getChildAt(i);
            if(child.getVisibility() != View.GONE){
                child.layout(1,i*mScreenHeight,i2,(i+1)*mScreenHeight);
            }
        }
    }

在代碼中主要还是修改每个View的top和bottom属性,接下来我们就让他滑动了


    //滑动事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastY = y;
                mStart = getScrollY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (!mScroller.isFinished) {
                    mScroller.abortAnimation();
                }
                int dy = mLastY - y;
                if (getScrollY() < 0) {
                    dy = 0;
                }
                if (getScrollY() > getHeight() - mScreenHeight) {
                    dy = 0;
                }
                scrollBy(0, dy);
                mLastY = y;
                break;
            case MotionEvent.ACTION_UP:
                mEnd = getScrollY();
                int dScrollY = mEnd - mStart;
                if (dScrollY > 0) {
                    if (dScrollY < mScreenHeight / 3) {
                        mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
                    } else {
                        mScroller.startScroll(0, getScrollY(), 0, mScreenHeight - dScrollY);
                    }
                } else {
                    if (-dScrollY < mScreenHeight / 3) {
                        mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
                    } else {
                        mScroller.startScroll(0, getScrollY(), 0, -mScreenHeight - dScrollY);
                    }
                }
                break;
        }
        postInvalidate();
        return true;
    }

別忘了加這個方法

 @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            scrollTo(0, mScroller.getCurrY());
        }
    }

OK,大致的思路即是如此

八.事件拦截机制分析

这章讲的是一个事件拦截机制的一些基本概念,,当Android系统扑捉到用户的各种输入事件之后,如何准确的传递给真正需要这个事件的控件尼?其实Android提供了一套非常完善的事件传递,处理机制,来帮助开发者完成准确的事件分配和处理

要想了解拦截机制,我们首先要知道什么事触摸事件,一般MotionEvent提供的手势,我们常用的几个DOWN,UP,MOVE什么的

在MotionEvent中封装了很多东西,比如获取坐标点event.getX()和getRawX()获取

但是我们这次讲的是事件拦截,那什么才是事件拦截,打个比方View和ViewGroup都要这个事件,这里就产生了事件拦截,其实就是一层层递减的意义,一般ViewGroup我们需要重写三个方法


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

而我们的View就只要两个了

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

这个其实还是有些繁琐的讲概念,推荐大家看一篇博客:http://blog.csdn.net/chunqiuwei/article/details/41084921挺详细的

我们用四个View来覆盖(以下图片来自上面博客,尊重原博主)

得到的点击事件Log

我们就可以很明确的知道他们的执行过程了

还有两种情况,就是A拦截

B拦截

这里大致的简写了以下,因为我觉得上面那篇博客总结的比我好,我们通过前面的几种情况分析得到了大致的思想,在后面的学习,我们应该结合源码,你才会有更深的认识,如果是初学者,还是不建议看源码,会头大

第三章大致的写完了,虽然有点简写,但是也只是一个自我学习的笔记罢了,大多数还是要自己去领悟,写这篇还是花了很长的一段事件,最近也在离职,深圳如果有合适的工作可以联系下我

笔记下载链接:http://pan.baidu.com/s/1c0U7k2W 密码:9v0g

Android群英传笔记——第三章:Android控件架构与自定义控件讲解的更多相关文章

  1. Android群英传笔记——第七章:Android动画机制和使用技巧

    Android群英传笔记--第七章:Android动画机制和使用技巧 想来,最 近忙的不可开交,都把看书给冷落了,还有好几本没有看完呢,速度得加快了 今天看了第七章,Android动画效果一直是人家中 ...

  2. Android群英传笔记——第六章:Android绘图机制与处理技巧

    Android群英传笔记--第六章:Android绘图机制与处理技巧 一直在情调,时间都是可以自己调节的,不然世界上哪有这么多牛X的人 今天就开始读第六章了,算日子也刚好一个月了,一个月就读一半,这效 ...

  3. Android群英传笔记——第四章:ListView使用技巧

    Android群英传笔记--第四章:ListView使用技巧 最近也是比较迷茫,但是有一点点还是要坚持的,就是学习了,最近离职了,今天也是继续温习第四章ListView,也拖了其实也挺久的了,list ...

  4. Android群英传笔记——第五章:Android Scroll分析

    Android群英传笔记--第五章:Android Scroll分析 滑动事件算是Android比较常用的效果了,而且滑动事件他本身也是有许多的知识点,今天,我们就一起来耍耍Scroll吧 一.滑动效 ...

  5. Android群英传笔记——第十章:Android性能优化

    Android群英传笔记--第十章:Android性能优化 随着Android应用增多,功能越来越复杂,布局也越来越丰富了,而这些也成为了阻碍一个应用流畅运行,因此,对复杂的功能进行性能优化是创造高质 ...

  6. Android群英传笔记系列三 view的自定义:实现一个模拟下载

    1.实现效果:动态显示进度(分别显示了整个的动态改变的过程,然后完成后,弹出一个对话框)       2.实现过程:可以分为绘制一个圆,圆弧和文本三部分,然后在MainAcitivity中通过线程模拟 ...

  7. 《Android群英传》读书笔记 (2) 第三章 控件架构与自定义控件详解 + 第四章 ListView使用技巧 + 第五章 Scroll分析

    第三章 Android控件架构与自定义控件详解 1.Android控件架构下图是UI界面架构图,每个Activity都有一个Window对象,通常是由PhoneWindow类来实现的.PhoneWin ...

  8. Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验

    Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高 ...

  9. Android群英传笔记——第九章:Android系统信息和安全机制

    Android群英传笔记--第九章:Android系统信息和安全机制 本书也正式的进入尾声了,在android的世界了,不同的软件,硬件信息就像一个国家的经济水平,军事水平,不同的配置参数,代表着一个 ...

随机推荐

  1. list标准函数的模拟

    ;反序 ( ) -> ( ) (define (rvs x) (let recur ((x x)(res '())) (if (null? x) res (recur (cdr x) (cons ...

  2. activiti 数据库连接配置

    1.1.1. 前言 在activiti 动态配置 activiti 监听引擎启动和初始化(高级源码篇)一文中,我们讲解了如何动态的配置DataSource 当我们程序配置了DataSource,act ...

  3. Android传感器

    Android传感器 开发传感器应用 1. 获取传感器管理者对象 // 获取传感器管理者对象 SensorManager mSensorManager = (SensorManager) getSys ...

  4. Hessian源码分析--HessianProxy

    在上一篇博客 Hessian源码分析--HessianProxyFactory 中我们了解到,客户端获得的对象其实是HessianProxy生成的目标对象,当调用目标对象的方法时,会调用Hessian ...

  5. JSP连接MySQL时出现--错误:Access denied for user 'root'@'localhost' (using password: YES)'解决方案

    用代码进行用户验证的时候总是出现这个错误,翻译一下,应该是root用户的是权限的问题没有放开. 那就想办法解决一下吧,具体的来说可以有这样的几种方式. 解决方法,首先想到的是先重启一下MySQL服务吧 ...

  6. iOS开发中 常用枚举和常用的一些运算符(易错总结)

    1.色值的随机值: #define kColorValue arc4random_uniform(256)/255.0 // arc4random_uniform(256)/255.0; 求出0.0~ ...

  7. Uva - 1593 - Alignment of Code

    直接用<iomanip>的格式输出,setw设置输出宽度,setiosflags(ios::left)进行左对齐. AC代码: #include <iostream> #inc ...

  8. 【Android 系统开发】 编译 Android文件系统 u-boot 内核 并烧写到 OK-6410A 开发板上

    博客地址 : http://blog.csdn.net/shulianghan/article/details/40299813  本篇文章中用到的工具源码下载 : -- ok-6410A 附带的 A ...

  9. 显示 Ubuntu 11.10 的 终端窗口

    显示 Ubuntu 11.10 的 终端窗口 一.点击左上角的图标 -> 在search框里搜索termial . 二.快捷键:Ctrl+Alt+t.

  10. Android代码(Handler的运用),HttpURLConnection的应用,将url图片地址转换成图片。

     1 布局文件, <LinearLayout 秒, 抛异常 conn.connect();      // 开始链接 int responseCode = conn.getResponseC ...