文档章节

自定义View

Gnepux
 Gnepux
发布于 2016/12/27 15:30
字数 1546
阅读 44
收藏 2

View是什么?

View是屏幕善的一块矩形区域,它负责来显示一个区域,并且响应这个区域内的事件。可以说,手机屏幕上任意可以看得见的地方都是View。

对于Activity来说,我们通过setContentView(view)添加的布局到Activity上,实际上都是添加到了Activity内部的DecorView上面,这个DecorView,其实就是一个FrameLayout,因此我们的布局实际上添加到了FrameLayout里面。

View是怎么工作的?

View的工作流程分为两部分,第一部分是显示在屏幕上的过程,第二部分是响应屏幕上的触摸事件的过程。

对于显示在屏幕上的过程:是View从无到有,经过测量大小(Measure)、确定在屏幕中的位置(Layout)、以及最终绘制在屏幕上(Draw)这一系列的过程。

对于响应屏幕上的触摸事件的过程:则是Touch事件的分发过程。

Measure()、Layout()方法是final修饰的,无法重写,Draw()虽然不是final的,但是耶不建议重写该方法。

如何自定义View?

View为我们提供了onMeasure()、onLayout()、onDraw()这样的方法,其实所谓的自定义View,也就是对这三个方法的重写过程。

measure()

View的onMeasure()方法如下:

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

onMeasure通过父View传递过来额大小和模式,以及自身的背景图片的大小得出自身最终的大小,通过setMeasuredDimension()方法设置给mMeasuredWidth和mMeasuredHeight。

普通的View的onMeasure逻辑大同小异,基本都是测量自身内容和背景,然后根据父View传递过来的MeasureSpec进行最终的大小判定,例如TextView会根据文字的长度,文字的大小,文字行高,文字的宽度,显示方法,背景图片以及父View传递过来的模式和大小最终确定自身的大小。

MeasureSpec

这是Android中自己定义的测量规格。因为测量过程中,系统会把我们代码或者布局中设置的View的LayoutParams转换成MeasureSpec,然后通过MeasureSpec来测量确定View的宽高。

MeasureSpec由32位int值组成,高2位表示的测量模式specMode,后30位代表的是测量大小specSize

public static class MeasureSpec {  
       private static final int MODE_SHIFT = 30;  
       private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
  
       /** 测量模式:父View不对子View的大小做任何限制,可以是子布局需要的任意大小 
        * Measure specification mode: The parent has not imposed any constraint 
        * on the child. It can be whatever size it wants. 
        */  
       public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
  
       /** 已经为子View给出了确切的值 相当于给View的LayoutParams指定了具体数值,或者match_parent. 
        * Measure specification mode: The parent has determined an exact size 
        * for the child. The child is going to be given those bounds regardless 
        * of how big it wants to be. 
        */  
       public static final int EXACTLY     = 1 << MODE_SHIFT;  
  
       /**子View可以跟自己的测量大小一样大。对应于View的LayoutParams的wrap_content 
        * Measure specification mode: The child can be as large as it wants up 
        * to the specified size. 
        */  
       public static final int AT_MOST     = 2 << MODE_SHIFT;  
  
       /**根据 size和mode 去生成一个MeasureSpec值 
        * Creates a measure specification based on the supplied size and mode. 
        * 
        * The mode must always be one of the following: 
        *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> 
        *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> 
        *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> 
        * @return the measure specification based on size and mode 
        */  
       public static int makeMeasureSpec(int size, int mode) {  
           if (sUseBrokenMakeMeasureSpec) {  
               return size + mode;  
           } else {  
               return (size & ~MODE_MASK) | (mode & MODE_MASK);  
           }  
       }  
  
       /**获取测量模式 
        * Extracts the mode from the supplied measure specification. 
        * 
        * @param measureSpec the measure specification to extract the mode from 
        * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, 
        *         {@link android.view.View.MeasureSpec#AT_MOST} or 
        *         {@link android.view.View.MeasureSpec#EXACTLY} 
        */  
       public static int getMode(int measureSpec) {  
           return (measureSpec & MODE_MASK);  
       }  
  
       /**获取测量值大小 
        * Extracts the size from the supplied measure specification. 
        * 
        * @param measureSpec the measure specification to extract the size from 
        * @return the size in pixels defined in the supplied measure specification 
        */  
       public static int getSize(int measureSpec) {  
           return (measureSpec & ~MODE_MASK);  
       }  
  
       static int adjust(int measureSpec, int delta) {  
           return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));  
       }  
  
   }

MeasureSpec是由父布局和View自身的LayoutParams来决定的

PS:经过实际使用,父View的布局为match_parent或指定具体数字时是EXACTLY;

父View的布局为wrap_content时是AT_MOST。

 

经过measure后,我们就可以通过getMeasuredWidth/Height获取View的宽高。

layout()

普通的View对于layout方法直接空实现即可。

draw()

draw()的过程就是绘制View到屏幕上的规程,draw()的执行遵守如下步骤:

/* 
 * Draw traversal performs several drawing steps which must be executed 
 * in the appropriate order: 
 * 
 *      1. Draw the background(绘制背景) 
 *      2. If necessary, save the canvas' layers to prepare for fading(保存画布的图层来准备色变) 
 *      3. Draw view's content(绘制内容) 
 *      4. Draw children(绘制children) 
 *      5. If necessary, draw the fading edges and restore layers(画出褪色的边缘和恢复层) 
 *      6. Draw decorations (scrollbars for instance)(绘制装饰 比如scollbar) 
 */ 

一般2和5是可以跳过的。

在TextView中在该方法中绘制文字、光标和CompoundDrawable、ImageView中相对简单,只是绘制了图片。View的绘制主要通过dispatchDraw()。

View的几个比较重要的方法

requestLayout - View重新调用一次layout过程。

invalidate - View重新调用一次draw过程。

postInvalidate - 在非UI线程发起invalidate动作。

forceLayout - 标识View在下一次重绘,需要重新调用layout过程。

Demo

一个自定义方块,循环显示不同颜色

/**
 * Created by xupeng on 16/12/25.
 */

public class MyCustomView extends View {

    private Paint mPaint = null;

    private int[] colors = {Color.BLUE, Color.GREEN, Color.YELLOW};

    public MyCustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, 0, defStyleAttr);
        int color = typedArray.getColor(R.styleable.MyCustomView_custom_color, Color.BLUE);
        mPaint = new Paint();
        mPaint.setColor(color);
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (true){
                    mPaint.setColor(colors[i++ % 3]);
                    postInvalidate();
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getDefaultSize(getMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getMinimumHeight(), heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(final Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, 200, 300, mPaint);
    }

}

 

参考链接:

View原理简介

http://blog.csdn.net/u011733020/article/details/50849475

帮你搞定Android自定义View

http://www.jianshu.com/p/84cee705b0d3

Android手把手教您自定义ViewGroup

http://blog.csdn.net/lmj623565791/article/details/38339817/

为什么自定义ViewGroup ondraw方法不会被调用

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1014/1765.html

关于getMeasuredHeight和getHeight区别(getMeasureHeight是xml或代码中制定的测量高度,setMeasuredHeight; getHeight是实际显示出来的高度,通过view.layout()方便可以改变其值)

http://blog.csdn.net/qq_29951983/article/details/50571840

 

© 著作权归作者所有

上一篇: 自定义ViewGroup
下一篇: TODO
Gnepux
粉丝 3
博文 96
码字总数 77557
作品 0
南京
私信 提问

暂无文章

Java System 类

Java System 类 System 系统类 主要用于获取系统的属性数据。 System类常用的方法: arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 一般 从指定源数...

Hellation
29分钟前
0
0
Nginx源码安装和调优技巧

本文内容 Nginx与apache的对比 实战1:在“腾讯云主机”上源码编译安装Nginx 实战2:Nginx调优之隐藏版本信息防止黑客扫描识别漏洞 实战3:设置网页缓存 实验环境: 使用RHEL6.5/centos6.5 6...

寰宇01
31分钟前
1
0
linux jenkins添加windows节点,实现自动化部署

背景: 要基于jenkins的做代码自动更新部署,现状是jenkins在linux上,目标服务器的tomcat在windows上,如何将代码从linux发送到windows未找到合适方案,并且后续如何远程调用执行windows批处...

shzwork
37分钟前
0
0
MySQL同表更新与查询冲突

MySQL version: 5.5 MySQL报错: You can't specify target table 'document_basic' for update in FROM clause 原因:MySQL不支持对同表同时更新+查询 解决方案:查询结果使用中间表接收,或......

Hzhodor
38分钟前
0
0
最长不重复字符子串

给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。 这个有点像最长前缀匹配,简单的想到的就是暴力搜索一下,得到最大的子串,这样就是时间复杂度比较大,可以看到里面有两个f...

woshixin
43分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部