基于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:
设置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"
显示如下:
蓝色方框是matrix变换之前的位置。
为当前View设置点击事件,可以发现,仅在点击View的绘制区域(非白色区域)时触发onClick方法,在点击蓝色方框内的空白区域时不会触发。
有以下结论:
- matrix变换影响整个View的显示(包括子View)
- matrix与scrollX srollY互不影响
- 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是这个样子的:
1、ScrollView添加两个属性:
scrollview的背景是米黄色。可以看到,在可以滑动的边上,呈现出淡出的效果。
2、android:scrollbarSize
控制滚动条的尺寸。
android:scrollbarStyle
控制滚动条的风格。有四个值:insideOverlay,insideInset,outsideOverlay,outsideInset
。
为了方便区分这四个风格,我们为ScrollView设置一个30dp的padding, 同时scrollbarSize设置为30dp。四种风格如下:
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。如下:
透明黑色的部分是盖的新的window,由图可知,按钮被window遮挡的部分也可以响应点击事件。
现在我们给根布局LinearLayout(按钮本身或包含它的布局都可以)加上android:filterTouchesWhenObscured="true"
从图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
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()));
}
});
需要注意的是,这里只影响显示的内容,不影响点击区域。
同时,也不是任意的outline形状都支持裁剪。只有矩形、圆角矩形、圆形这三种支持。其他形状,包括椭圆都不支持。Outline类的方法canClip()
可获取当前outline是否支持裁剪。其他设置方法可查看Outline类源码。
11 android:tooltipText
api26新加。给view设置一个功能提示。与电脑软件中,鼠标停留在一个按钮上,会弹出一个功能说明一样。这里通过长按触发。
如下面的TextView:
12 android:keyboardNavigationCluster
api26新增
google链接
13自动填充
api26新增的自动填充功能,目前使用很少,不再介绍。
没搞明白的几个
android:scrollIndicators
ViewGroup的属性
1 android:clipChildren
该属性默认是true
看这样一个布局:
打开开发者模式的布局边界开关,显示如下:
在外层的FrameLayout上加上android:clipChildren="false"
,显示如下:
该属性对应的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等。
对如下布局:
显示效果为:
为ScrollView添加android:clipToPadding="false"
后:
可以看到,在overscroll时,两段阴影部分的位置也变了。
3 动画相关
android:layoutAnimation
、android: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;
...
}
默认情况下:
可以看到,两个ScrollView可以独立滑动。设置android:splitMotionEvents="false"
可以看到,只能滑动一个ScrollView,该ScrollView是先获得Down事件的那个。
至此,View和ViewGroup除focus相关,其他属性基本都学习了。
ScrollView
1 android:fillViewport
设置ScrollView是否对内容进行拉伸,来适配ScrollView的高度,默认false。对应方法如下:
public void setFillViewport(boolean fillViewport) {
if (fillViewport != mFillViewport) {
mFillViewport = fillViewport;
requestLayout();
}
}
看如下布局:
修改android:fillViewport="true"
:
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:alpha
,android: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。
例子如下:
注:虽然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:
显示效果为:
TextView
1 android:editable
该属性决定TextView是否可编辑,默认值为false,由以下方法获得:
protected boolean getDefaultEditable() {
return false;
}
EditText就是覆写该方法,返回了true。
若可编辑,则会创建Editor
,以接收处理各种KeyEvent。
如下例子:
需要注意的是,这里必须设置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" />
除光标外,其他三个设置在MIUI上无效
11 android:textIsSelectable
设置文本是否可被选中,选中态会弹出上下文菜单,默认false。如:
该菜单可以通过长按或者双击唤出。
12 文字上下边距
除了可以设置padding,还有两个方法:
android:firstBaselineToTopHeight="30dp"
android:lastBaselineToBottomHeight="30dp"
参考Android 9.0关于字体的新特性