zoukankan      html  css  js  c++  java
  • 第三章 Android控件架构与自定义控件详解

    这章的重要性不言而喻,按照以下几个方面进行介绍:

    1. Android控件架构
    2. View的测量与绘制
    3. 自定义控件的三种方式
    4. 事件的拦截机制

    3.1   Android控件架构

    3.2 View的测量

      Android系统提供了一个设计短小精悍的类---MeasureSpec类,通过它帮助我们测量。MeasureSpec是一个32位的int值,其中高2位是测量的模式,低30位是测量的大小。

    注意:View类默认的onMeasure()方法只支持EXACTLY模式。所以在自定义控件的时候不重写onMeasure的话,就只能使用EXACTLY模式。控件可以响应你指定的具体的宽高或match_parent属性。而如果要让自定义view支持wrap_content属性,那么就要重写onMeasure。

    package com.fightzhao.gesturedetectordemo.ui;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.View;
    
    /**
     * Created by fightzhao on 16-3-7.
     */
    public class TestView extends View {
        public TestView(Context context) {
            super(context);
        }
    
        public TestView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(measureWidth(widthMeasureSpec), measureHight(heightMeasureSpec));
        }
    
        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;
        }
    
        private int measureHight(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;
        }
    }

    3.3 View的绘制
      这个书中写的不是太明白,难点在于创建Canvas时候为何在其构造函数中传入的参数是bitmap。
    Canvas相当于是一个画板,使用Paint在上面作画了。通常是用过继承view,重写其中的onDraw()方法来完成绘图。
    那么什么是Canvas呢?一般情况下,可以使用重写的View类中的onDraw方法来绘图。onDraw中有一个参数就是Canvas canvas对象。使用这个对象就可以进行绘图了,而在其他地方,通常需要代码创建一个Canvas对象:
        Canvas canvas = new Canvas(bitmap);
    当创建一个Canvas对象的时候,为什么要传进去一个bitmap对象呢?如果不传入一个bitmap的话,编译器虽然不会报错,,但是一般我是不会这么做的。原因如下:
    1. 传进去的bitmap和与这个bitmap创建的canvas是紧密联系在一起的,这个过程我们称之为装载画布。这个bitmap用来存储所有绘制在Canvas上的像素信息。所以通过这种方式创建的canvas对象后,后面调用所有的Canvas.drawXXX方法都发生在这个bitmap上。

    2. 如果在view类的onDraw()方法,通过如下代码:可以了解canvas和bitmap的关系:

          canvas.drawBitmap(bitmap1,0,0,null);

          canvas.drawBitmap(bitmap2,0,0,null);

      而对于bitmap2,我们将它装载到另一个Canvas对象中: Canvas mCanvas = new Canvas(bitmap2);

      在其他地方使用Canvas对象的绘图方法在装载bitmap2的Canvas对象进行绘图: mCanvas.drawXXX

        通过mCanvas将绘制效果作用在了bitmap2上,再刷新View的时候,就会发现通过onDraw()方法画出来的bitmap2已经发生了改变,这就是因为bitmap2承载了mCanvas上所进行的绘图操作。虽然我们也使用了Canva的绘制API,但是实际上并没有直接将图形绘制在onDraw()方法制定的画布上,而是通过改变bitmap,然后让view进行重新绘制,从而显示改变后的bitmap。

    3.6 自定义View

    1. 可以分为以下三类:
      1. 对现有的控件进行拓展,需要重写onDraw()比如TextView。那么什么时候使用呢?这种效果不方便通过布局的组合方式展现的时候,往往需要动态或者静态的展现一些不规则图形的时候,即需要重写onDraw().。采用这种方式需要自己手动的自己支持wrap_content,并且可以padding。
      2. 通过组合来实现新的控件:
        1. 继承ViewGroup派生特殊的Layout:这种方法用在需要自定义布局的时候,即除了LinearLayout、RelativeLayout、FrameLayout这几种系统的布局外,我们重新定义一种新的布局的时候,当某种效果看起来很像几种View组合在一起的时候,可以采用这种方法实现。采用这种方法要麻烦一点,需要合适的处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局。
        2. 继承特定的ViewGroup(比如LinearLayout):这种方法比较常见,当效果看起来像是几个View组合在一起的效果的时候,可以用这种方法实现。不需要自己处理ViewGroup的测量和布局,注意和上面的区别,一般来说上面能实现的,这个也一定可以实现,两者差别在于上面的更加接近View的底层。、
      3. 重写View,实现全新的效果。

    依次介绍这几种重写view的方法:

    1. 第一种是对现有View进行拓展:

          

    package com.fightzhao.gesturedetectordemo.ui;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.widget.TextView;
    
    /**
     * Created by fightzhao on 16-3-8.
     */
    public class MyTextView extends TextView {
        private Paint mPaint1;
        private Paint mPaint2;
    
        public MyTextView(Context context) {
            super(context);
            init();
        }
    
        public MyTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
    
        private void init() {
            mPaint1 = new Paint();
            mPaint1.setColor(Color.BLUE);
            mPaint1.setStyle(Paint.Style.FILL);
            mPaint1.setStrokeWidth(2);
    
            mPaint2 = new Paint();
            mPaint2.setColor(Color.YELLOW);
            mPaint2.setStyle(Paint.Style.FILL);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
    //        在回调父类方法前,实现自己的逻辑,对TextView来说就是绘制文本之前
    //        绘制外层矩形
            int paddingLeft = getPaddingLeft();
            int paddingRight = getPaddingLeft();
            int paddingTop = getPaddingLeft();
            int paddingBoom = getPaddingLeft();
    
            int width = getWidth() - paddingLeft - paddingRight;
            int height = getHeight() - paddingTop - paddingBoom;
            canvas.drawRect(0, 0, getMeasuredWidth(), getHeight(), mPaint1);
    
    //        绘制内层圆
            int radius = Math.min(width, height) / 2;
            canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint2);
            canvas.save();
    
            canvas.translate(10, 0);
    
            super.onDraw(canvas);
    //        在回调父类方法后,实现自己的逻辑,对TextView来说就是绘制文本内容之后
            canvas.restore();
        }
    }

      

      再来实现一个稍微复杂的:预览图如下:

    具体代码:

    package com.fightzhao.gesturedetectordemo.ui;
    
    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 fightzhao on 16-3-7.
     */
    public class TestView extends TextView {
        private int mViewWidth,mTranslate;
        private Paint mPaint;
        private LinearGradient mLinearGradient;
        private Matrix mMatrix;
        public TestView(Context context) {
            super(context);
        }
    
        public TestView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mPaint = new Paint();
            mPaint.setColor(Color.YELLOW);
            mPaint.setStyle(Paint.Style.FILL);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(measureWidth(widthMeasureSpec), measureHight(heightMeasureSpec));
        }
    
        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;
        }
    
        private int measureHight(int measureSpec) {
            int result = 0;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            if (specMode == MeasureSpec.EXACTLY) {
                result = specSize;
            } else {
                result = 150;
                if (specMode == MeasureSpec.AT_MOST) {
                    result = Math.min(result, specSize);
                }
            }
            return result;
        }
    
    
        @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,0xfffffff,Color.BLUE
                    },null, Shader.TileMode.CLAMP);
                    mPaint.setShader(mLinearGradient);
                     mMatrix = new Matrix();
                }
            }
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if(mMatrix!=null){
                mTranslate += mViewWidth / 5;
                if (mTranslate > 2 * mViewWidth) {
                    mTranslate = -mViewWidth;
                }
                mMatrix.setTranslate(mTranslate, 0);
                mLinearGradient.setLocalMatrix(mMatrix);
                postInvalidateDelayed(100);
            }
        }
    }
    

      这里有两个要点:一个是设置的wrap_content的实现。需要重写onMeasure()方法。

    第二个:设置的效果直接看书。

          

  • 相关阅读:
    课题:快速建立自己的外链资源圈
    【干货分享】常用端口服务对照表
    【经验分享(续篇)】Trachtenberg system(特拉亨伯格速算系统)
    网站渗透测试原理及详细过程
    渗透测试入门DVWA 教程1:环境搭建
    CTF---密码学入门第七题 杯酒人生
    CTF---密码学入门第六题 古典密码
    CTF---密码学入门第五题 传统知识+古典密码
    CTFCrackTools在Windows下显示A Java Exception has occurred的解决方案
    CTF---密码学入门第四题 困在栅栏里的凯撒
  • 原文地址:https://www.cnblogs.com/fightzhao/p/5251954.html
Copyright © 2011-2022 走看看