欢迎来到【酒店住宿系统源码查询】【h5红包互换2021源码】【万单哥源码推荐靠谱】viewgroup源码-皮皮网网站!!!

皮皮网

【酒店住宿系统源码查询】【h5红包互换2021源码】【万单哥源码推荐靠谱】viewgroup源码-皮皮网 扫描左侧二维码访问本站手机端

【酒店住宿系统源码查询】【h5红包互换2021源码】【万单哥源码推荐靠谱】viewgroup源码

2024-11-23 07:22:43 来源:{typename type="name"/} 分类:{typename type="name"/}

1.android开发设置屏蔽录制
2.炫酷!这些都是Android中不规则形状View的布局实现!
3.Android UI绘制之View绘制的工作原理
4.自定义View(1)

viewgroup源码

android开发设置屏蔽录制

       é¡¹ç›®å¼€å‘中,为了用户信息的安全,会有禁止页面被截屏、录屏的需求。

       è¿™ç±»èµ„料,在网上有很多,一般都是通过设置Activity的Flag解决,如:

       //禁止页面被截屏、录屏getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);

       è¿™ç§è®¾ç½®å¯è§£å†³ä¸€èˆ¬çš„防截屏、录屏的需求。

       å¦‚果页面中有弹出Popupwindow,在录屏视频中的效果是:

       éžPopupwindow区域为黑色

       ä½†Popupwindow区域仍然是可以看到的

       å¦‚下面两张Gif图所示:

       æœªè®¾ç½®FLAG_SECURE,录屏的效果,如下图(git图片中间的水印忽略):

       è®¾ç½®äº†FLAG_SECURE之后,录屏的效果,如下图(git图片中间的水印忽略):

       åŽŸå› åˆ†æž

       çœ‹åˆ°äº†ä¸Šé¢çš„效果,我们可能会有疑问PopupWindow不像Dialog有自己的window对象,而是使用WindowManager.addView方法将View显示在Activity窗体上的。那么,Activity已经设置了FLAG_SECURE,为什么录屏时还能看到PopupWindow?

       æˆ‘们先通过getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);来分析下源码:

       1、Window.java

       //window布局参数private final WindowManager.LayoutParams mWindowAttributes =        new WindowManager.LayoutParams();//添加标识public void addFlags(int flags) {

       setFlags(flags, flags);

       }//通过mWindowAttributes设置标识public void setFlags(int flags, int mask) {        final WindowManager.LayoutParams attrs = getAttributes();

       attrs.flags = (attrs.flags&~mask) | (flags&mask);

       mForcedWindowFlags |= mask;

       dispatchWindowAttributesChanged(attrs);

       }//获得布局参数对象,即mWindowAttributespublic final WindowManager.LayoutParams getAttributes() {        return mWindowAttributes;

       }

       é€šè¿‡æºç å¯ä»¥çœ‹åˆ°ï¼Œè®¾ç½®window属性的源码非常简单,即:通过window里的布局参数对象mWindowAttributes设置标识即可。

       2、PopupWindow.java

       //显示PopupWindowpublic void showAtLocation(View parent, int gravity, int x, int y) {

       mParentRootView = new WeakReference<>(parent.getRootView());

       showAtLocation(parent.getWindowToken(), gravity, x, y);

       }//显示PopupWindowpublic void showAtLocation(IBinder token, int gravity, int x, int y) {        if (isShowing() || mContentView == null) {            return;

       }

       TransitionManager.endTransitions(mDecorView);

       detachFromAnchor();

       mIsShowing = true;

       mIsDropdown = false;

       mGravity = gravity;

       //创建Window布局参数对象

       final WindowManager.LayoutParams p =createPopupLayoutParams(token);

       preparePopup(p);

       p.x = x;

       p.y = y;

       invokePopup(p);

       }//创建Window布局参数对象protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();

       p.gravity = computeGravity();

       p.flags = computeFlags(p.flags);

       p.type = mWindowLayoutType;

       p.token = token;

       p.softInputMode = mSoftInputMode;

       p.windowAnimations = computeAnimationResource();        if (mBackground != null) {

       p.format = mBackground.getOpacity();

       } else {

       p.format = PixelFormat.TRANSLUCENT;

       }        if (mHeightMode < 0) {

       p.height = mLastHeight = mHeightMode;

       } else {

       p.height = mLastHeight = mHeight;

       }        if (mWidthMode < 0) {

       p.width = mLastWidth = mWidthMode;

       } else {

       p.width = mLastWidth = mWidth;

       }

       p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH

       | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;

       p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));        return p;

       }//将PopupWindow添加到Window上private void invokePopup(WindowManager.LayoutParams p) {        if (mContext != null) {

       p.packageName = mContext.getPackageName();

       }        final PopupDecorView decorView = mDecorView;

       decorView.setFitsSystemWindows(mLayoutInsetDecor);

       setLayoutDirectionFromAnchor();

       mWindowManager.addView(decorView, p);        if (mEnterTransition != null) {

       decorView.requestEnterTransition(mEnterTransition);

       }

       }

       é€šè¿‡PopupWindow的源码分析,我们不难看出,在调用showAtLocation时,会单独创建一个WindowManager.LayoutParams布局参数对象,用于显示PopupWindow,而该布局参数对象上并未设置任何防止截屏Flag。

       å¦‚何解决

       åŽŸå› æ—¢ç„¶æ‰¾åˆ°äº†ï¼Œé‚£ä¹ˆå¦‚何处理呢?

       å†å›žå¤´åˆ†æžä¸‹Window的关键代码:

       //通过mWindowAttributes设置标识public void setFlags(int flags, int mask) {        final WindowManager.LayoutParams attrs = getAttributes();

       attrs.flags = (attrs.flags&~mask) | (flags&mask);

       mForcedWindowFlags |= mask;

       dispatchWindowAttributesChanged(attrs);

       }

       å…¶å®žåªéœ€è¦èŽ·å¾—WindowManager.LayoutParams对象,再设置上flag即可。

       ä½†æ˜¯PopupWindow并没有像Activity一样有直接获得window的方法,更别说设置Flag了。我们再分析下PopupWindow的源码:

       //将PopupWindow添加到Window上private void invokePopup(WindowManager.LayoutParams p) {        if (mContext != null) {

       p.packageName = mContext.getPackageName();

       }

       final PopupDecorView decorView = mDecorView;

       decorView.setFitsSystemWindows(mLayoutInsetDecor);

       setLayoutDirectionFromAnchor();        //添加View

       mWindowManager.addView(decorView, p);        if (mEnterTransition != null) {

       decorView.requestEnterTransition(mEnterTransition);

       }

       }

       æˆ‘们调用showAtLocation,最终都会执行mWindowManager.addView(decorView, p);

       é‚£ä¹ˆæ˜¯å¦å¯ä»¥åœ¨addView之前获取到WindowManager.LayoutParams呢?

       ç­”案很明显,默认是不可以的。因为PopupWindow并没有公开获取WindowManager.LayoutParams的方法,而且mWindowManager也是私有的。

       å¦‚何才能解决呢?

       æˆ‘们可以通过hook的方式解决这个问题。我们先使用动态代理拦截PopupWindow类的addView方法,拿到WindowManager.LayoutParams对象,设置对应Flag,再反射获得mWindowManager对象去执行addView方法。

       é£Žé™©åˆ†æžï¼š

       ä¸è¿‡ï¼Œé€šè¿‡hook的方式也有一定的风险,因为mWindowManager是私有对象,不像Public的API,谷歌后续升级Android版本不会考虑其兼容性,所以有可能后续Android版本中改了其名称,那么我们通过反射获得mWindowManager对象不就有问题了。不过从历代版本的Android源码去看,mWindowManager被改的几率不大,所以hook也是可以用的,我们尽量写代码时考虑上这种风险,避免以后出问题。

       public class PopupWindow {

       ......    private WindowManager mWindowManager;

       ......

       }

       è€ŒaddView方法是ViewManger接口的公共方法,我们可以放心使用。

       public interface ViewManager{    public void addView(View view, ViewGroup.LayoutParams params);    public void updateViewLayout(View view, ViewGroup.LayoutParams params);    public void removeView(View view);

       }

       åŠŸèƒ½å®žçŽ°

       è€ƒè™‘到hook的可维护性和扩展性,我们将相关代码封装成一个独立的工具类吧。

       package com.ccc.ddd.testpopupwindow.utils;

       import android.os.Handler;

       import android.view.WindowManager;

       import android.widget.PopupWindow;

       import java.lang.reflect.Field;

       import java.lang.reflect.InvocationHandler;

       import java.lang.reflect.Method;

       import java.lang.reflect.Proxy;public class PopNoRecordProxy implements InvocationHandler {    private Object mWindowManager;//PopupWindow类的mWindowManager对象

       public static PopNoRecordProxy instance() {        return new PopNoRecordProxy();

       }    public void noScreenRecord(PopupWindow popupWindow) {        if (popupWindow == null) {            return;

       }        try {            //通过反射获得PopupWindow类的私有对象:mWindowManager

       Field windowManagerField = PopupWindow.class.getDeclaredField("mWindowManager");

       windowManagerField.setAccessible(true);

       mWindowManager = windowManagerField.get(popupWindow);            if(mWindowManager == null){                return;

       }            //创建WindowManager的动态代理对象proxy

       Object proxy = Proxy.newProxyInstance(Handler.class.getClassLoader(), new Class[]{ WindowManager.class}, this);            //注入动态代理对象proxy(即:mWindowManager对象由proxy对象来代理)

       windowManagerField.set(popupWindow, proxy);

       } catch (IllegalAccessException e) {

       e.printStackTrace();

       } catch (NoSuchFieldException e) {

       e.printStackTrace();

       }

       }

       @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        try {            //拦截方法mWindowManager.addView(View view, ViewGroup.LayoutParams params);

       if (method != null && method.getName() != null && method.getName().equals("addView")

       && args != null && args.length == 2) {                //获取WindowManager.LayoutParams,即:ViewGroup.LayoutParams

       WindowManager.LayoutParams params = (WindowManager.LayoutParams) args[1];                //禁止录屏

       setNoScreenRecord(params);

       }

       } catch (Exception ex) {

       ex.printStackTrace();

       }        return method.invoke(mWindowManager, args);

       }    /

**

       * 禁止录屏

       */

       private void setNoScreenRecord(WindowManager.LayoutParams params) {

       setFlags(params, WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);

       }    /

**

       * 允许录屏

       */

       private void setAllowScreenRecord(WindowManager.LayoutParams params) {

       setFlags(params, 0, WindowManager.LayoutParams.FLAG_SECURE);

       }    /

**

       * 设置WindowManager.LayoutParams flag属性(参考系统类Window.setFlags(int flags, int mask))

       

*

       * @param params WindowManager.LayoutParams

       * @param flags  The new window flags (see WindowManager.LayoutParams).

       * @param mask   Which of the window flag bits to modify.

       */

       private void setFlags(WindowManager.LayoutParams params, int flags, int mask) {        try {            if (params == null) {                return;

       }            params.flags = (params.flags & ~mask) | (flags & mask);

       } catch (Exception ex) {

       ex.printStackTrace();

       }

       }

       }

       Popwindow禁止录屏工具类的使用,代码示例:

          //创建PopupWindow

       //正常项目中,该方法可改成工厂类

       //正常项目中,也可自定义PopupWindow,在其类中设置禁止录屏

       private PopupWindow createPopupWindow(View view, int width, int height) {

       PopupWindow popupWindow = new PopupWindow(view, width, height);        //PopupWindow禁止录屏

       PopNoRecordProxy.instance().noScreenRecord(popupWindow);        return popupWindow;

       }   //显示Popupwindow

       private void showPm() {

       View view = LayoutInflater.from(this).inflate(R.layout.pm1, null);

       PopupWindow  pw = createPopupWindow(view,ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

       pw1.setFocusable(false);

       pw1.showAtLocation(this.getWindow().getDecorView(), Gravity.BOTTOM | Gravity.RIGHT, PopConst.PopOffsetX, PopConst.PopOffsetY);

       }

       å½•å±æ•ˆæžœå›¾ï¼š

炫酷!这些都是Android中不规则形状View的布局实现!

       在Android开发中,我们经常面临一个问题:如何创建非矩形形状的酒店住宿系统源码查询View?在官方提供的工具中,我们找不到直接创建非矩形View的解决方案。通常,开发者需要自己定义一个View,但这涉及到大量的代码重写,并且可能会导致一些特性的丢失,或产生bug。

       自定义View虽然可以实现非矩形形状,但其触摸区域仍然是矩形的。如果想要让特定区域的触摸无效,需要在`onTouchEvent`中进行复杂的计算。对于ViewGroup而言,h5红包互换2021源码这将更加复杂,因为需要重写`dispatchTouchEvent`。

       这时候,`ClipPathLayout`这个强大的布局出现了,它能够对子View的Path进行裁剪,完美解决了非矩形View的布局问题。

       `ClipPathLayout`,顾名思义,万单哥源码推荐靠谱就是一种可以裁剪子View绘制范围和触摸范围的布局,从而实现非矩形的View。

       那么,`ClipPathLayout`有什么实际用途呢?答案是:它可以让你的View以非矩形形状绘制,并且可以控制触摸范围,实现更自由的交互设计。

       让我们通过实例来看看`ClipPathLayout`的效果。例如,免授权H5棋牌源码你可以将一个方形裁剪成圆形,并且让圆形View的四个角成为触摸无效的区域。或者,你甚至可以使用它来制作不规则形状的按钮,如阴阳鱼的图案。

       那么,如何使用`ClipPathLayout`呢?首先,确保你已经添加了依赖库到你的跑步机下控板源码项目中。接着,在你的ViewGroup中添加一个子View,并使用`PathInfo`来配置Path,从而实现非矩形的布局。

       使用`ClipPathLayout`的过程相对简单,只需要几步就可以让你的View以非矩形形状展示,并且可以控制触摸区域。它的实现原理基于对Canvas的裁剪,通过Path信息控制Canvas的绘制范围。

       如果你想深入了解`ClipPathLayout`的工作原理,那么,它的工作机制实际上涉及到了对Path的生成、绘制以及触摸事件的处理。`ClipPathLayout`使用了Path来定义非矩形区域,并在Canvas绘制之前对Canvas进行裁剪,这样可以控制View的显示范围。对于触摸事件的处理,它通过在`dispatchTouchEvent`方法中判断触摸点是否在裁剪后的区域内来实现。

       如果你对`ClipPathLayout`的实现原理感兴趣,可以查阅相关源码进行深入学习。此外,`ClipPathLayout`还提供了自定义Path生成器的功能,允许你根据需求实现不同的非矩形布局。

       最后,`ClipPathLayout`不仅仅局限于简单的圆形或不规则形状的布局,通过自定义Path生成器,你可以实现更多复杂且独特的界面设计。对于那些追求界面创新的开发者来说,`ClipPathLayout`无疑是一个强大的工具。

Android UI绘制之View绘制的工作原理

        这是AndroidUI绘制流程分析的第二篇文章,主要分析界面中View是如何绘制到界面上的具体过程。

        ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联。

        measure 过程决定了 View 的宽/高, Measure 完成以后,可以通过 getMeasuredWidth 和 getMeasuredHeight 方法来获取 View 测量后的宽/高,在几乎所有的情况下,它等同于View的最终的宽/高,但是特殊情况除外。 Layout 过程决定了 View 的四个顶点的坐标和实际的宽/高,完成以后,可以通过 getTop、getBottom、getLeft 和 getRight 来拿到View的四个顶点的位置,可以通过 getWidth 和 getHeight 方法拿到View的最终宽/高。 Draw 过程决定了 View 的显示,只有 draw 方法完成后 View 的内容才能呈现在屏幕上。

        DecorView 作为顶级 View ,一般情况下,它内部会包含一个竖直方向的 LinearLayout ,在这个 LinearLayout 里面有上下两个部分,上面是标题栏,下面是内容栏。在Activity中,我们通过 setContentView 所设置的布局文件其实就是被加到内容栏中的,而内容栏id为 content 。可以通过下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通过 content.getChildAt(0) 可以得到设置的 view 。 DecorView 其实是一个 FrameLayout , View 层的事件都先经过 DecorView ,然后才传递给我们的 View 。

        MeasureSpec 代表一个位的int值,高2位代表 SpecMode ,低位代表 SpecSize , SpecMode 是指测量模式,而 SpecSize 是指在某种测量模式下的规格大小。

        SpecMode 有三类,如下所示:

        UNSPECIFIED

        EXACTLY

        AT_MOST

        LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。

        对于顶级View,即DecorView和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同确定;

        对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同决定;

        MeasureSpec一旦确定,onMeasure就可以确定View的测量宽/高。

        小结一下

        当子 View 的宽高采用 wrap_content 时,不管父容器的模式是精确模式还是最大模式,子 View 的模式总是最大模式+父容器的剩余空间。

        View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即测量、布局、绘制。其中 measure 确定 View 的测量宽/高, layout 确定 view 的最终宽/高和四个顶点的位置,而 draw 则将 View 绘制在屏幕上。

        measure 过程要分情况,如果只是一个原始的 view ,则通过 measure 方法就完成了其测量过程,如果是一个 ViewGroup ,除了完成自己的测量过程外,还会遍历调用所有子元素的 measure 方法,各个子元素再递归去执行这个流程。

        如果是一个原始的 View,那么通过 measure 方法就完成了测量过程,在 measure 方法中会去调用 View 的 onMeasure 方法,View 类里面定义了 onMeasure 方法的默认实现:

        先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源码:

        可以看到, getMinimumWidth 方法获取的是 Drawable 的原始宽度。如果存在原始宽度(即满足 intrinsicWidth > 0),那么直接返回原始宽度即可;如果不存在原始宽度(即不满足 intrinsicWidth > 0),那么就返回 0。

        接着看最重要的 getDefaultSize 方法:

        如果 specMode 为 MeasureSpec.UNSPECIFIED 即未指定模式,那么返回由方法参数传递过来的尺寸作为 View 的测量宽度和高度;

        如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精确模式,那么返回从 measureSpec 中取出的 specSize 作为 View 测量后的宽度和高度。

        看一下刚才的表格:

        当 specMode 为 EXACTLY 或者 AT_MOST 时,View 的布局参数为 wrap_content 或者 match_parent 时,给 View 的 specSize 都是 parentSize 。这会比建议的最小宽高要大。这是不符合我们的预期的。因为我们给 View 设置 wrap_content 是希望View的大小刚好可以包裹它的内容。

        因此:

        如果是一个 ViewGroup,除了完成自己的 measure 过程以外,还会遍历去调用所有子元素的 measure 方法,各个子元素再递归去执行 measure 过程。

        ViewGroup 并没有重写 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 这几个方法专门用于测量子元素。

        如果是 View 的话,那么在它的 layout 方法中就确定了自身的位置(具体来说是通过 setFrame 方法来设定 View 的四个顶点的位置,即初始化 mLeft , mRight , mTop , mBottom 这四个值), layout 过程就结束了。

        如果是 ViewGroup 的话,那么在它的 layout 方法中只是确定了 ViewGroup 自身的位置,要确定子元素的位置,就需要重写 onLayout 方法;在 onLayout 方法中,会调用子元素的 layout 方法,子元素在它的 layout 方法中确定自己的位置,这样一层一层地传递下去完成整个 View 树的 layout 过程。

        layout 方法的作用是确定 View 本身的位置,即设定 View 的四个顶点的位置,这样就确定了 View 在父容器中的位置;

        onLayout 方法的作用是父容器确定子元素的位置,这个方法在 View 中是空实现,因为 View 没有子元素了,在 ViewGroup 中则进行抽象化,它的子类必须实现这个方法。

        1.绘制背景( background.draw(canvas); );

        2.绘制自己( onDraw );

        3.绘制 children( dispatchDraw(canvas) );

        4.绘制装饰( onDrawScrollBars )。

        dispatchDraw 方法的调用是在 onDraw 方法之后,也就是说,总是先绘制自己再绘制子 View 。

        对于 View 类来说, dispatchDraw 方法是空实现的,对于 ViewGroup 类来说, dispatchDraw 方法是有具体实现的。

        通过 dispatchDraw 来传递的。 dispatchDraw 会遍历调用子元素的 draw 方法,如此 draw 事件就一层一层传递了下去。dispatchDraw 在 View 类中是空实现的,在 ViewGroup 类中是真正实现的。

        如果一个 View 不需要绘制任何内容,那么就设置这个标记为 true,系统会进行进一步的优化。

        当创建的自定义控件继承于 ViewGroup 并且不具备绘制功能时,就可以开启这个标记,便于系统进行后续的优化;当明确知道一个 ViewGroup 需要通过 onDraw 绘制内容时,需要关闭这个标记。

        参考:《Android开发艺术探索》

自定义View(1)

        经历过前面三篇啰啰嗦嗦的基础篇之后,终于到了进阶篇,正式进入解析自定义View的阶段。

        至于本章节为什么要叫进阶篇(虽然讲的是基础的内容),因为从本篇开始,将会逐渐揭开自定义View的神秘面纱,每一篇都将比上一篇内容更加深入,利用所学的知识能够制作更加炫酷自定义View,就像在台阶上一样,每一篇都更上一层, 帮助大家一步步走向人生巅峰,出任CEO,赢取白富美。 误,是帮助大家更加了解那些炫酷的自定义View是如何制作的,达到举一反三的效果。

        作为一个<b>有(hui)追(zhuang)求(B)</b>的程序员,肯定想做一些让人眼前一亮的程序效果,但是系统提供的那些一般很难满足,为了<b>梦(zhuang)想(B)</b>就必须要学习一些自定义View。下面我们就了解一些自定义View相关的东西。

        自定义ViewGroup一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout,包含有子View。

        例如:一个应用内的底部导航条中的条目,一般都是上面为图标,下面是文字,那么这两个就可以用自定义ViewGroup组合成为一个Veiw,提供两个属性分别用来设置文字和图片即可,这样使用起来会方便很多。

        在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View,不包含子View。

        例如:定义一个支持自动加载网络图片的ImageView,或制作一种特殊的动画效果。

        <b>一般来说,自定义View在大多数情况下都有替代方案,利用图片或者组合动画来实现,但是使用后者可能会面临内存耗费过大,制作麻烦更诸多问题。</b>

        View的构造函数有四种重载分别如下

        可以看出,关于View构造函数的参数有多有少,先排除几个不常用的,留下常用的再研究。

        <b>有四个参数的构造函数在API的时候才添加上,我一般不使用,暂不考虑。</b>

        有三个参数的构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或Activity所用的Theme中的默认Style,且只有在明确调用的时候才会生效,以系统中的ImageButton为例说明:

        <b>注意:即使你在View中使用了Style这个属性也不会调用三个参数的构造函数,所调用的依旧是两个参数的构造函数。</b>

        <b>由于三个参数的构造函数第三个参数一般不用,暂不考虑,第三个参数的具体用法会在以后用到的时候详细介绍。</b>

        排除了两个之后,只剩下一个参数和两个参数的构造函数,他们的详情如下:

        以下方法调用的是<b>一个参数</b>的构造函数:

        以下方法调用的是<b>两个参数</b>的构造函数:

        关于构造函数先讲这么多,关于如何自定义属性和使用attrs中的内容,在后面会详细讲解,目前只需要知道这两个构造函数在何时调用即可。

        ========

        测量View大小使用的是onMeasure函数,我们可以从这两个参数取出宽高的相关数据:

        从上面可以看出 onMeasure 函数中有 widthMeasureSpec 和 heightMeasureSpec 这两个 int 类型的参数, 毫无疑问他们是和宽高相关的, <b>但它们其实不是宽和高, 而是由宽、高和各自方向上对应的模式来合成的一个值:</b>

        在int类型的位二进制位中,-这两位表示模式,~0这三十位表示宽和高的实际值。

        以数值(二进制为: )为例(其中模式和实际数值是连在一起的,为了展示我将他们分开了):

        实际上关于上面的东西了解即可,在实际运用之中只需要记住有三种模式,用 MeasureSpec 的 getSize是获取数值, getMode是获取模式即可。

        如果对View的宽高进行修改了,<b>不要调用super.onMeasure(widthMeasureSpec,heightMeasureSpec);</b>

        要调用<b>setMeasuredDimension(widthsize,heightsize);</b> 这个函数。

        ======

        这个函数在视图大小发生改变时调用:

        onSizeChanged如下:

        可以看出,它又四个参数,分别为 宽度,高度,上一次宽度,上一次高度。

        这个函数比较简单,我们只需关注 宽度(w), 高度(h) 即可,这两个参数就是View最终的大小。

        =========

        <b>确定布局的函数是onLayout,它用于确定子View的位置,在自定义ViewGroup中会用到,他调用的是子View的layout函数。</b>

        不过关于View的layout函数我们一般无需关注,因为在一般情况下我们只需关注View自身的坐标系即可,除非View状态与在父VIew所处位置相关。

        在自定义ViewGroup中,onLayout一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。

        四个参数分别为:

        具体可以参考 坐标系 这篇文章:

        PS:关于onLayout这个函数在讲解自定义ViewGroup的时候会详细讲解。

        ========

        onDraw是实际绘制的部分,也就是我们真正关心的部分,使用的是Canvas绘图。

        关于Canvas绘图另分一章吧,本来想写一些关于Canvas基本操作的的,可是篇幅太长了QAQ, 留个尾巴下一篇再写吧,毕竟Canvas绘图也是一个比较庞大的东西,也不是三言两语就能讲明白的,就到这里吧。

        ======

        自定义完View之后,一般会对外暴露一些接口,用于操作View的相关属性,控制View的状态等,或者需要监听View的变化,具体还是稍后再讲吧(继续挖坑)。

        PS :实际上ViewGroup是View的一个子类。

        View

        ViewGroup

        View.MeasureSpec

        onMeasure,MeasureSpec源码 流程 思路详解

        Android中自定义样式与View的构造函数中的第三个参数defStyle的意义

        android view构造函数研究

        Android View构造方法第三参数使用方法详解

        Android 自定义View onMeasure方法的实现

        Android API指南(二)自定义控件之 onMeasure

        Android中View的绘制过程 onMeasure方法简述