View源码——android:XX属性

基于api28

常用属性不再赘述,只看一些不常用的或者新添加的属性。

1 padding

android:paddingHorizontal //同时设置左右padding
android:paddingVertical //同时设置上下padding

以上属性api26添加。

2 scroll

android:scrollX
android:scrollY

以上两个属性对应的成员变量为mScrollX mScrollX,在绘制之前,会执行

canvas.translate(-mScrollX, -mScrollY);

同时影响当前View的绘制,及其children的绘制。但对当前view的背景没有影响!
偏移量 > 0, 内容向右下偏移;
偏移量 < 0, 内容向左上偏移。

注:在xml中设置View的继承类的android:scrollX、android:scrollY不一定起作用,依赖于具体的实现。
比如ScrollView,由于在滚动时会判断getChildCount() > 0,而初始化时还没有子View,所以不起作用。

如下图为自定义的View,背景为黑色,在onDraw方法中画了一个圆,宽高均为100dp:

View源码——android:XX属性_第1张图片
图1 原图

设置android:scrollX=-50dp,背景不移动,内容右移:

View源码——android:XX属性_第2张图片
图2 android:scrollX=-50dp

至于为什么不影响背景,后续再学习。

3 matrix变换


android:transformPivotX
android:transformPivotY

android:rotation
android:rotationX
android:rotationY

android:translationX
android:translationY
android:translationZ

android:scaleX
android:scaleY

对图2设置如下属性:

        android:transformPivotX="50dp"
        android:transformPivotY="50dp"
        android:rotation="30"
        android:scaleX="0.8"
        android:translationX="30dp"

显示如下:

View源码——android:XX属性_第3张图片
图3 matrix变换

蓝色方框是matrix变换之前的位置。
为当前View设置点击事件,可以发现,仅在点击View的绘制区域(非白色区域)时触发onClick方法,在点击蓝色方框内的空白区域时不会触发。
有以下结论:

  1. matrix变换影响整个View的显示(包括子View)
  2. matrix与scrollX srollY互不影响
  3. matrix会影响可点击区域

有的同学还知道有个android:elevation属性,它和android:translationZ有什么区别呢?

android系统中,View所在的坐标系是一个三维的,View最终的坐标可以通过getX getY getZ三个方法得到。看一下这三个方法的实现:

    public float getX() {
        return mLeft + getTranslationX();
    }

    public float getY() {
        return mTop + getTranslationY();
    }

    public float getZ() {
        return getElevation() + getTranslationZ();
    }

为了便于理解,我们可以将mLeft mTop elevation看做是View本身的坐标值,translationX tranlationY tranlationZ看做是偏移量。则 最终坐标 = 坐标值 + 偏移量。

x y z的set方法设置的就是偏移量:

    public void setX(float x) {
        setTranslationX(x - mLeft);
    }

    public void setY(float y) {
        setTranslationY(y - mTop);
    }

    public void setZ(float z) {
        setTranslationZ(z - getElevation());
    }

4 fitsSystemWindows

android:fitsSystemWindows

默认false。详细分析见 View源码——fitSystemWindows详解

5 saveEnabled

android:saveEnabled

设置View是否意外销毁时,会调用onSaveInstanceState方法来保存状态。默认为true。详见View源码——saveInstanceState

6 duplicateParentState

设置当前view是否使用父view的状态,默认false。该状态主要影响drawable的显示。View源码——duplicateParentState

7 滚动相关

以如下布局为例:

    

        

            
        
    

此时的ui是这个样子的:

View源码——android:XX属性_第4张图片
图4

1、ScrollView添加两个属性:

    
View源码——android:XX属性_第5张图片
图5

scrollview的背景是米黄色。可以看到,在可以滑动的边上,呈现出淡出的效果。

2、android:scrollbarSize控制滚动条的尺寸。
android:scrollbarStyle控制滚动条的风格。有四个值:insideOverlay,insideInset,outsideOverlay,outsideInset
为了方便区分这四个风格,我们为ScrollView设置一个30dp的padding, 同时scrollbarSize设置为30dp。四种风格如下:

View源码——android:XX属性_第6张图片
图6 insideOverlay
View源码——android:XX属性_第7张图片
图7 insideInset
View源码——android:XX属性_第8张图片
图8 outsideOverlay
View源码——android:XX属性_第9张图片
图9 outsideInset

3、android:overScrollMode控制scrollView滑到头时,是否显示阴影。图4可以很明显看出来滑到头时的阴影部分。有三个取值:

  • never: 从不显示
  • always:滑到头时显示
  • ifContentScrolls(默认):若内容过短,scrollView无法滑动,则不显示;否则显示。

4、android:isScrollContainer与软键盘有关。参考android:isScrollContainer 属性的作用

5、android:verticalScrollbarPosition 垂直滚动条的显示位置,有三个值:left right defaultPosition,很简单不再多说。

8 android:filterTouchesWhenObscured

view被其他窗口遮挡时,是否过滤触摸事件。默认false。只看说明很难理解,下面看一个例子。

布局:




    

        

java

public class ViewActivity extends Activity implements View.OnClickListener {
    private View view;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view);

        view = new View(getApplicationContext());
        view.setBackgroundColor(0x88000000);

        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.width = 720;
        params.gravity = Gravity.LEFT | Gravity.TOP;
        params.format = PixelFormat.TRANSPARENT;
        params.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        getWindowManager().addView(view, params);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt1:
                Toast.makeText(this, "hahha", Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getWindowManager().removeView(view);
    }
}

代码很简单,在activity的页面上盖了一个window。如下:

View源码——android:XX属性_第10张图片
图10

透明黑色的部分是盖的新的window,由图可知,按钮被window遮挡的部分也可以响应点击事件。
现在我们给根布局LinearLayout(按钮本身或包含它的布局都可以)加上android:filterTouchesWhenObscured="true"

View源码——android:XX属性_第11张图片
图11

从图11可以看到,按钮被遮挡的部分,已经不再响应点击事件了。

9 android:stateListAnimator

关于这个属性的讲解有很多,不再赘述

10 outline相关

api21开始才有的。api21之后,view添加elevation、transitionZ的方法,由二维空间变成了三维空间。在Z轴上,可以通过outline来为view设置阴影等。

xml文件可以设置以下三个属性:

        android:outlineAmbientShadowColor
        android:outlineSpotShadowColor
        android:outlineProvider

第一个用来设置环境光颜色,不过肉眼难以分辨。默认黑色。
第二个用来设置阴影的主题颜色。默认黑色。
第三个设置outline的获取方式,分别为:background(默认)、none、bounds、paddedBounds。区别下面会讲。

先看个例子:
xml

    
View源码——android:XX属性_第12张图片
图12 outline阴影效果

android:outlineProvider对应的java方法为:

    public void setOutlineProvider(ViewOutlineProvider provider) {
        mOutlineProvider = provider;
        invalidateOutline();
    }

ViewOutlineProvider类很简单:

public abstract class ViewOutlineProvider {
    public abstract void getOutline(View view, Outline outline);
}

android:outlineProvider的四个值,对应四个默认的ViewOutlineProvider
background(默认)

    public static final ViewOutlineProvider BACKGROUND = new ViewOutlineProvider() {
        @Override
        public void getOutline(View view, Outline outline) {
            Drawable background = view.getBackground();
            if (background != null) {
                background.getOutline(outline);
            } else {
                outline.setRect(0, 0, view.getWidth(), view.getHeight());
                outline.setAlpha(0.0f);
            }
        }
    };

none则为null。

bounds

    public static final ViewOutlineProvider BOUNDS = new ViewOutlineProvider() {
        @Override
        public void getOutline(View view, Outline outline) {
            outline.setRect(0, 0, view.getWidth(), view.getHeight());
        }
    };

paddedBounds

    public static final ViewOutlineProvider PADDED_BOUNDS = new ViewOutlineProvider() {
        @Override
        public void getOutline(View view, Outline outline) {
            outline.setRect(view.getPaddingLeft(),
                    view.getPaddingTop(),
                    view.getWidth() - view.getPaddingRight(),
                    view.getHeight() - view.getPaddingBottom());
        }
    };

此外,view还有一个setClipToOutline的方法,可以根据outline裁剪内容
对上面的例子,在java代码中设置:

        ImageView vImage = findViewById(R.id.image);
        vImage.setClipToOutline(true);
        vImage.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                outline.setOval(new Rect(0, 0, view.getHeight(), view.getHeight()));
            }
        });
View源码——android:XX属性_第13张图片
图13

需要注意的是,这里只影响显示的内容,不影响点击区域。
同时,也不是任意的outline形状都支持裁剪。只有矩形、圆角矩形、圆形这三种支持。其他形状,包括椭圆都不支持。Outline类的方法canClip()可获取当前outline是否支持裁剪。其他设置方法可查看Outline类源码。

11 android:tooltipText

api26新加。给view设置一个功能提示。与电脑软件中,鼠标停留在一个按钮上,会弹出一个功能说明一样。这里通过长按触发。
如下面的TextView:

View源码——android:XX属性_第14张图片
图14

12 android:keyboardNavigationCluster

api26新增

google链接

View源码——android:XX属性_第15张图片
图15

13自动填充

api26新增的自动填充功能,目前使用很少,不再介绍。

没搞明白的几个

android:scrollIndicators


ViewGroup的属性

1 android:clipChildren

该属性默认是true

看这样一个布局:

    

        

            

打开开发者模式的布局边界开关,显示如下:

图16

在外层的FrameLayout上加上android:clipChildren="false",显示如下:

图17

该属性对应的java方法如下:

    public void setClipChildren(boolean clipChildren) {
        boolean previousValue = (mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN;
        if (clipChildren != previousValue) {
            setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren);
            for (int i = 0; i < mChildrenCount; ++i) {
                View child = getChildAt(i);
                if (child.mRenderNode != null) {
                    child.mRenderNode.setClipToBounds(clipChildren);
                }//child在绘制时,也会自行调用setClipToBounds方法
            }
            invalidate(true);
        }
    }

该方法只会影响直接子view。最终调用的是RenderNode的setClipToBounds方法。setClipToBounds方法会根据view的边界对显示区域进行裁剪。

RenderNode后续再讲。感兴趣的同学也可以查询其他资料。也可以把它暂时看做是canvas。

上例中,图16的显示应该不必说了。关于图17的显示,为什么是这个样子的呢?对于Button,其父view默认setClipChildren(true),所以画布会根据Button的边界进行一次裁剪;而对于内部的FrameLayout,其父view设置了setClipChildren(false),所以画布并不会根据它的边界进行裁剪,所以就显示出来了。

需要注意的是,虽然Button显示完全了,但是其父view边界之外的区域不能响应点击事件。所以setClipChildren方法的注释是这样写的:

默认情况下,子view在绘制之前会根据它的边界进行裁剪。ViewGroup可以覆写该方法以作动画之用。

我的理解是,看看就得了,别摸!!!

2 android:clipToPadding

若该属性为true,并且设置了padding,则ViewGroup会添加一个CLIP_TO_PADDING_MASK的标志位。该标志位会影响

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

方法中的cancas,如下:
ViewGroup#dispatchDraw

        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
        if (clipToPadding) {
            clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                    mScrollX + mRight - mLeft - mPaddingRight,
                    mScrollY + mBottom - mTop - mPaddingBottom);
        }

对画布进行裁剪,留出padding的余量。
该属性一般用在可滚动的view上,如ScrollView,ListView,GridView,RecyclerView等。

对如下布局:

    

        

            
        
    

显示效果为:

View源码——android:XX属性_第16张图片
图18

为ScrollView添加android:clipToPadding="false"后:

View源码——android:XX属性_第17张图片
图19

可以看到,在overscroll时,两段阴影部分的位置也变了。

3 动画相关

android:layoutAnimationandroid:animateLayoutChanges
参考android 动画系列 (3) - layoutAnimation 视图动画,说的很详细。

4 android:splitMotionEvents

对应的方法如下:

    public void setMotionEventSplittingEnabled(boolean split) {
        // TODO Applications really shouldn't change this setting mid-touch event,
        // but perhaps this should handle that case and send ACTION_CANCELs to any child views
        // with gestures in progress when this is changed.
        if (split) {
            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
        } else {
            mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
        }
    }

true: 父类将多点触控分配给对应的view
false: 父类将多点触控分配给第一个获得触摸事件的view

该属性默认是true。如下:

    public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        initViewGroup();
        //该方法设置了的属性才会有默认值。对于XML文件没有设置的属性,不用理会该方法中的默认值。
        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
    }

    private void initViewGroup() {
        ....
        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
        ...
    }

默认情况下:

View源码——android:XX属性_第18张图片
图20

可以看到,两个ScrollView可以独立滑动。设置android:splitMotionEvents="false"

View源码——android:XX属性_第19张图片
图21

可以看到,只能滑动一个ScrollView,该ScrollView是先获得Down事件的那个。

至此,View和ViewGroup除focus相关,其他属性基本都学习了。


ScrollView

1 android:fillViewport

设置ScrollView是否对内容进行拉伸,来适配ScrollView的高度,默认false。对应方法如下:

    public void setFillViewport(boolean fillViewport) {
        if (fillViewport != mFillViewport) {
            mFillViewport = fillViewport;
            requestLayout();
        }
    }

看如下布局:

    


        
    
View源码——android:XX属性_第20张图片
屏幕快照 2019-11-02 上午11.34.28.png

修改android:fillViewport="true"

View源码——android:XX属性_第21张图片
屏幕快照 2019-11-02 上午11.38.02.png

ImageView

关于ScaleType比较基础,这里不再多说了。

1 android:adjustViewBounds

使View的尺寸和Drawable保持同样的宽高比。对应的方法为:

    public void setAdjustViewBounds(boolean adjustViewBounds) {
        mAdjustViewBounds = adjustViewBounds;
        if (adjustViewBounds) {
            setScaleType(ScaleType.FIT_CENTER);
        }
    }

可以看到,该属性会设置ScaleType为FIT_CENTER,这样可以保证Drawable刚好填满View。该属性对应的成员变量为mAdjustViewBounds
不过ImageView解析XML属性的顺序如下:

        setAdjustViewBounds(a.getBoolean(R.styleable.ImageView_adjustViewBounds, false));
        ...
        final int index = a.getInt(R.styleable.ImageView_scaleType, -1);
        if (index >= 0) {
            setScaleType(sScaleTypeArray[index]);
        }

所以如果XML同样设置了ScaleType,则以开发者设置的为准。

mAdjustViewBounds在onMeasure时会影响View的尺寸。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        int w;
        int h;
        ...
        // We are allowed to change the view's width
        boolean resizeWidth = false;
        // We are allowed to change the view's height
        boolean resizeHeight = false;

        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        ...
            w = mDrawableWidth;
            h = mDrawableHeight;
            ...
            //检查是否设置了mAdjustViewBounds
            if (mAdjustViewBounds) {
                //只有在任意一边没有设置固定尺寸的情况下才可以resize
                resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
                resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
                desiredAspect = (float) w / (float) h;
            }
        ...
        if (resizeWidth || resizeHeight) {
            //根据宽度重新计算高度,或者根据高度重新计算宽度
            ...
        } else {
            ...
        }

        setMeasuredDimension(widthSize, heightSize);
    }

这里要注意的是,ImageView的宽高至少有一边是wrapContent,mAdjustViewBounds才能生效。并且View的尺寸只是和Drawable的尺寸保持宽高比一致,并不是大小一致。通常Drawable还是要被缩放。同时,因为ImageView设置Drawable的方法都要调用requestLayout(),所以ImageView的尺寸会动态调整。

2 android:drawableAlpha

不同于View的属性android:alphaandroid:drawableAlpha用来设置Drawable的透明度。对应的方法为:

    public void setImageAlpha(int alpha) {
        setAlpha(alpha);
    }

    @Deprecated
    public void setAlpha(int alpha) {
        alpha &= 0xFF;          // keep it legal
        if (mAlpha != alpha) {
            mAlpha = alpha;
            mColorMod = true;
            applyColorMod();
            invalidate();
        }
    }

取值范围0~255。View的setAlpha:

    public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
        ensureTransformationInfo();
        if (mTransformationInfo.mAlpha != alpha) {
            setAlphaInternal(alpha);
            if (onSetAlpha((int) (alpha * 255))) {
                mPrivateFlags |= PFLAG_ALPHA_SET;
                // subclass is handling alpha - don't optimize rendering cache invalidation
                invalidateParentCaches();
                invalidate(true);
            } else {
                mPrivateFlags &= ~PFLAG_ALPHA_SET;
                invalidateViewProperty(true, false);
                mRenderNode.setAlpha(getFinalAlpha());
            }
        }
    }

用来设置整个View的透明度,取值范围0~1。

例子如下:



    

    

    

View源码——android:XX属性_第22张图片
图22

注:虽然ImageView有android:drawableAlpha属性,但直接在XML中设置,编译时会报错,提示attribute android:drawableAlpha is private。所以上例使用了tools命名空间,截图是预览页面。
虽然XML中不能设置该属性,但我们可以通过java代码调用setImageAlpha方法进行设置。不清楚google为什么这么搞,好奇怪,难道是bug?

3 android:cropToPadding

    public void setCropToPadding(boolean cropToPadding) {
        if (mCropToPadding != cropToPadding) {
            mCropToPadding = cropToPadding;
            requestLayout();
            invalidate();
        }
    }

该属性与ViewGroup的android:clipToPadding功能类似。在ImageView#onDraw方法中:

            if (mCropToPadding) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                        scrollX + mRight - mLeft - mPaddingRight,
                        scrollY + mBottom - mTop - mPaddingBottom);
            }

            canvas.translate(mPaddingLeft, mPaddingTop);

            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            mDrawable.draw(canvas);

同样是裁剪画布的操作。
对如下两个ImageView:

    

    

显示效果为:

View源码——android:XX属性_第23张图片
图23

TextView

1 android:editable

该属性决定TextView是否可编辑,默认值为false,由以下方法获得:

    protected boolean getDefaultEditable() {
        return false;
    }

EditText就是覆写该方法,返回了true。
若可编辑,则会创建Editor,以接收处理各种KeyEvent。

如下例子:

    
View源码——android:XX属性_第24张图片
图24

需要注意的是,这里必须设置android:focusable="true"android:focusableInTouchMode="true",否则TextView无法获取焦点。

2 限制输入的字符类型

参考Android EditText限制输入字符的5种实现方式

3 android:selectAllOnFocus

默认false,对应方法:

    @android.view.RemotableViewMethod
    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
        createEditorIfNeeded();
        mEditor.mSelectAllOnFocus = selectAllOnFocus;

        if (selectAllOnFocus && !(mText instanceof Spannable)) {
            setText(mText, BufferType.SPANNABLE);
        }
    }

以EditText为例,设置为true,在获取焦点时,会选中全部内容

4 链接

android:autoLink设置是否将电话、邮箱、网址等显示为链接形式。默认都不显示为链接形式。
android:linksClickable设置链接是否可点击。默认true

5 尺寸相关

android:ems
控制TextView的宽度,只在android:layout_width="wrap_content"时有效。ems表示字符的宽度,可粗略理解为1ems相当于1个汉字的宽度。英文的非等宽字体每个字符宽度不同,所以1ems所能容下的数量也不同。

android:lines android:height
控制TextView的高度,只在android:layout_height="wrap_content"时有效。并且两个属性互斥,看源码:

    public void setLines(int lines) {
        mMaximum = mMinimum = lines;
        mMaxMode = mMinMode = LINES;

        requestLayout();
        invalidate();
    }
    public void setHeight(int pixels) {
        mMaximum = mMinimum = pixels;
        mMaxMode = mMinMode = PIXELS;

        requestLayout();
        invalidate();
    }

此外还有单独的android:maxEms等属性设置。

以上属性影响的是TextView的尺寸,与内容无关,注意与android:maxLength区分开。android:maxLength控制显示的最大字符数,实现如下:

        if (maxlength >= 0) {
            setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
        } else {
            setFilters(NO_FILTERS);
        }

通过LengthFilter控制最大字符数。

6 android:includeFontPadding

默认是true。设置为false,可以一定程度去掉上下的留白。注意,和设置padding属性是毫无关系的。

7 android:cursorVisible

设置是否隐藏掉光标。

8 android:textScaleX

设置横向拉伸/压缩内容

9 android:freezesText

该属性决定TextView被意外销毁时,是否保存当前内容。对应的java方法:

    public void setFreezesText(boolean freezesText) {
        mFreezesText = freezesText;
    }
    public boolean getFreezesText() {
        return mFreezesText;
    }

看源码是如何起作用的:

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();

        // Save state if we are forced to
        final boolean freezesText = getFreezesText();
        ...
            SavedState ss = new SavedState(superState);

            if (freezesText) {
                if (mText instanceof Spanned) {
                    final Spannable sp = new SpannableStringBuilder(mText);

                    if (mEditor != null) {
                        removeMisspelledSpans(sp);
                        sp.removeSpan(mEditor.mSuggestionRangeSpan);
                    }

                    ss.text = sp;
                } else {
                    ss.text = mText.toString();
                }
            }
            ...
        return superState;
    }

可以看到设置freezesText后,在onSaveInstanceState时就可以保存当前内容。
需要注意的是,想要保存内容,还需要设置当前视图树唯一的id,原因见View源码——saveInstanceState

EditText覆写了get方法:

    @Override
    public boolean getFreezesText() {
        return true;
    }

所以在有唯一id的情况下,默认会正确保存当前内容。

10 cursor|handle相关

    
        android:textSize="19dp" />
View源码——android:XX属性_第25张图片
光标和handle
handleLeft和handleRight

除光标外,其他三个设置在MIUI上无效

11 android:textIsSelectable

设置文本是否可被选中,选中态会弹出上下文菜单,默认false。如:

    
View源码——android:XX属性_第26张图片
device-2019-11-01-151930.png

该菜单可以通过长按或者双击唤出。

12 文字上下边距

除了可以设置padding,还有两个方法:

        android:firstBaselineToTopHeight="30dp"
        android:lastBaselineToBottomHeight="30dp"

参考Android 9.0关于字体的新特性


你可能感兴趣的:(View源码——android:XX属性)