Android难点之——自定义View(中)

注:以下笔记都是通过《Android群英传》这本书整理而来。

〇、自定义View前篇

1、我们不能机械地记忆所有绘图的API,而是要让这些API为你所用,结合现实中的绘图方法,甚至是Photoshop的技巧,才能设计出更好的自定义View。
2、一个用户觉得熟悉的控件才是好的控件,切记勿追求华而不实。
3、View中比较重要的回调方法:

  • onFinishInflate():从XML加载组件后回调。
  • onSizeChanged():组件大小改变时。
  • onMeasure():回调该方法来进行测量。
  • onLayout():回调该方法来确定显示的位置。
  • onTouchEvent():监听到触摸事件时回调。

一、对现有控件进行扩展

案例一:

1.png

part1:在构造中创建两支画笔,一支画背景一支画边框。

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

part2:在onDraw方法中绘制。

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint1);
    //绘制内层矩形
    canvas.drawRect(10,10,getMeasuredWidth()-10,getMeasuredHeight()-10,mPaint2);
    canvas.save();
    //绘制文字前平移10像素
    canvas.translate(10,10);
    //在回调父方法前,实现自己的逻辑,对TextView来说就是在绘制文本内容前
    super.onDraw(canvas);
    canvas.restore();
}

案例二:

闪动的文字效果.png

part1:
1、在onSizeChanged中初始化一些对象,是因为在该方法中组件的大小才真正改变,才能拿到组件的长宽被这些对象所用。
2、利用Paint对象的Shader渲染器,通过设置一个不断变化的LinearGradient,并使用带该属性(渲染器)的Paint来绘制要显示的文字。
3、最关键的就是使用getPaint()方法获取当前绘制TextView的Paint对象,并给Paint设置原生TextView没有的LinearGradient属性。

@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(); // 注意这个地方不要写成new Paint() 
            //参数一为渐变起初点坐标x位置,参数二为y轴位置,参数三和四分辨对应渐变终点,
            //参数五是参与渐变效果的颜色集合
            //参数六是定义每个颜色处于的渐变相对位置,这个参数可以为null,如果为null表示所有的颜色按顺序均匀的分布
            //最后参数为平铺方式
            mLinearGradient = new LinearGradient(
                    0,0,mViewWidth,0,new int[]{
                    Color.BLUE,0xffffffff,Color.BLUE
            },null, Shader.TileMode.CLAMP);
            mPaint.setShader(mLinearGradient);
            mGradientMatrix = new Matrix();
        }
    }
}

part2:通过矩阵的方式来不断平移渐变效果,在绘制文字时,产生动态的闪动效果。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if(mGradientMatrix!=null){
        mTranslate += mViewWidth/10;
        if(mTranslate > 2 * mViewWidth){
            mTranslate = -mViewWidth;
        }
        mGradientMatrix.setTranslate(mTranslate,0);
        mLinearGradient.setLocalMatrix(mGradientMatrix);
        postInvalidateDelayed(50);
    }
}

二、创建复合控件

书上的案例太简单,代码有点多这里就不贴了。
简单总结一下就是分为四个步骤:
1、自定义属性

1、在res资源目录下values目录下创建attrs.xml属性定义文件。


    
        
    

2、在构造中用代码获取xml布局中自定义的那些属性。

TypeArray ta = context.obtainStyledAttributes(attr,R.styleable.控件名);
属性类型 属性值 = ta.get属性类型(R.styleable.属性名);
ta.recycler();//调用recycler完成资源回收

2、组合控件

1、为创建的组件元素赋值,值就来源于我们在引用的xml文件中给对应属性的赋值。

Button btn1 = new Button(context);
btn1.setTextColor(属性值);

2、为组件元素设置响应的布局元素。

LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
params .addRule(ALIGN_PARENT_RIGHT,TRUE);

3、添加到ViewGroup。

addView(btn1,params);

3、定义接口。(略过)
4、暴露接口给调用者。(略过)

三、重写View来实现全新的控件

案例三:

Android难点之——自定义View(中)_第1张图片
案例三.png

part1:在构造方法中初始化对象。

WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
int screenWidth = wm.getDefaultDisplay().getWidth();
//圆心
mCircleXY = screenWidth/2;
//圆的半径
mRadius = (float) (screenWidth*0.5/2);
//指定圆弧的外轮廓矩形区域
mArcRectF = new RectF((float)(screenWidth*0.1),(float)(screenWidth*0.1),(float)(screenWidth*0.9),(float)(screenWidth*0.9));
mCirclePaint = new Paint();
mCirclePaint.setColor(Color.BLUE);
mArcPaint = new Paint();
mArcPaint.setStyle(Paint.Style.STROKE);
mArcPaint.setStrokeWidth(50);
mArcPaint.setColor(Color.GREEN);
mTextPaint = new Paint();
mTextPaint.setColor(Color.BLACK);
mShowText = "哈哈哈哈";
mShowTextSize = 40;

part2:分别绘制圆、圆弧、文字。

@Override
protected void onDraw(Canvas canvas) {
    //绘制圆
    canvas.drawCircle(mCircleXY,mCircleXY,mRadius,mCirclePaint);
    //绘制弧线(
    //  参数1:指定圆弧的外轮廓矩形区域,
    //  参数2:圆弧起始角度,单位为度
    //  参数3:圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度
    //  参数4:如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形
    //  参数5:绘制圆弧的画板属性,如颜色,是否填充等。)
    canvas.drawArc(mArcRectF,270,270,false,mArcPaint);
    //绘制文字
    canvas.drawText(mShowText,0,mShowText.length(),mCircleXY,mCircleXY+(mShowTextSize/4),mTextPaint);
}

相关资料:
android中canvas.drawText参数的介绍以及绘制一个文本居中的案例
Android的DrawText详解
如何“任性”使用Android的drawText()
drawArc方法介绍
案例四:

Android难点之——自定义View(中)_第2张图片
案例四.png

part1:先在构造中初始化一些对象

WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mWidth = wm.getDefaultDisplay().getWidth();
mPaint = new Paint();
mPaint.setColor(Color.BLUE);

part2:在组件改变位置的时候给Paint增加一个LinearGradient渐变效果。

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = getWidth();
    mRectHeight= getHeight();
    mRectWidth = (int)(mWidth*0.6/mRectCount);
    LinearGradient mLinearGradient = new LinearGradient(0,0,mRectWidth,mRectHeight,
            Color.YELLOW,Color.BLUE, Shader.TileMode.CLAMP);
    mPaint.setShader(mLinearGradient);
}

part3:在onDraw中循环绘制。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    for (int i = 0;i

今天敲了一遍书上的案例,有些地方还不是很消化,例如绘制文字那块还是略模糊,明天继续更自定义ViewGroup。

四、自定义ViewGroup

1、ViewGroup的目的就是为了对其子View进行管理。
2、ViewGroup通常要重写onMeasure()方法对子View进行测量,重写onLayout()确定子View的位置,重写onTouchEvent()增加响应事件。

案例5:
//TODO:待插入gif图
part1:在构造函数中初始化一些对象:

mScroller = new Scroller(context);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mScreenHeight = wm.getDefaultDisplay().getHeight();

part2:在onMeasure中给子View进行测量。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int count = getChildCount();
    for (int i=0;i

part3:遍历设定每个子View需要摆放的位置,直接通过调用子View的layout方法,将具体的参数传入即可。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount = getChildCount();
    //设置ViewGroup的高度
    MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
    //这里让每个子View都显示完整的一屏
    mlp.height = mScreenHeight*childCount;
    setLayoutParams(mlp);
    for (int i = 0;i

part4:在onTouchEvent中实现滚动的逻辑和"粘性"的逻辑。

 @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

你可能感兴趣的:(Android难点之——自定义View(中))