我们在自定义View的过程中,通常会让用户通过自定义属性值来控制View的显示效果。那么我们应该如何自定义属性和使用这些属性呢?
第一:我们需要在工程目录下res/values新建一个attr.xml文件,在该文件中定义我们自己的自定义的属性名称。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="myTextView">
<attr name="mColor" format="color"/>
<attr name="mBoolean" format="boolean"/>
<attr name="mDimension" format="dimension"/>
<attr name="mFloat" format="float"/>
<attr name="mInteger" format="integer"/>
<attr name="mString" format="string"/>
<attr name="mEnum" format="enum"/>
<attr name="mFlag">
<flag name="one" value="1"/>
<flag name="two" value="2"/>
</attr>
<attr name="mFraction" format="fraction"/>
<attr name="mReference" format="reference"/>
</declare-styleable>
</resources>
第二: 我们就可以在我们布局文件使用这些自定义属性了。
<com.lgy.typearray.MyTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
lgy:mColor="@color/material_blue_grey_900"
lgy:mBoolean="true"
lgy:mDimension="@dimen/abc_action_bar_progress_bar_size"
android:text="Hello World!" />
第三:我们此时就可以在我们自定义View的构造函数中获取我们在布局文件中设置的对应值。
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
mPaint.setColor(typedArray.getColor(R.styleable.myTextView_mColor, Color.RED));
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
float mDimension = typedArray.getDimension(R.styleable.myTextView_mDimension, 14f);
Log.i(TAG, "mDimension:" + mDimension);
int mDimensionSize = typedArray.getDimensionPixelSize(R.styleable.myTextView_mDimension, 14);
Log.i(TAG, "mDimensionSize:" + mDimensionSize);
int mDimensionPixelOffset = typedArray.getDimensionPixelOffset(R.styleable.myTextView_mDimension,14);
Log.i(TAG, "mDimensionPixelOffset:" + mDimensionPixelOffset);
typedArray.recycle();
}
到此使用方法就介绍好了。
下面我想在介绍的就是我们在attr.xml中 format对应值。
1、color 代表颜色值。
在attr.xml中我们可以<attr name="mColor" format="color"/>
,
在我们布局文件中我们可以lgy:mColor="@color/mytextView"
,
颜色mytextView是我们在定义<color name="mytextView">#5896ae</color>
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
typedArray.recycle();
运行代码结果图:
2、boolean代表真假true false
在attr.xml中我们可以<attr name="mBoolean" format="boolean"/>
,
在我们布局文件中我们可以lgy:mBoolean="true"
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
typedArray.recycle();
运行代码结果图:
3、float代表浮点数
在attr.xml中我们可以<attr name="mFloat" format="float"/>
,
在我们布局文件中我们可以lgy:mFloat="5"
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
float mFloat = typedArray.getFloat(R.styleable.myTextView_mFloat,0.0f);
Log.i(TAG,"mFloat:" + mFloat);
typedArray.recycle();
运行代码结果图:
4、integer 代表整形
在attr.xml中我们可以<attr name="mInteger" format="integer"/>
,
在我们布局文件中我们可以lgy:mInteger="16"
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
float mFloat = typedArray.getFloat(R.styleable.myTextView_mFloat,0.0f);
Log.i(TAG,"mFloat:" + mFloat);
int mInteger = typedArray.getInteger(R.styleable.myTextView_mInteger,1);
Log.i(TAG,"mInteger:" + mInteger);
int mInt = typedArray.getInt(R.styleable.myTextView_mInteger,2);
Log.i(TAG,"mInt:" + mInt);
typedArray.recycle();
运行代码结果图:
5、string 代表字符串
在attr.xml中我们可以<attr name="mString" format="string"/>
,
在我们布局文件中我们可以lgy:mString="自定义字符串"
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
float mFloat = typedArray.getFloat(R.styleable.myTextView_mFloat,0.0f);
Log.i(TAG,"mFloat:" + mFloat);
int mInteger = typedArray.getInteger(R.styleable.myTextView_mInteger,1);
Log.i(TAG,"mInteger:" + mInteger);
int mInt = typedArray.getInt(R.styleable.myTextView_mInteger,2);
Log.i(TAG,"mInt:" + mInt);
String mString = typedArray.getString(R.styleable.myTextView_mString);
Log.i(TAG,"mString:" + mString);
typedArray.recycle();
运行代码结果图:
6、enum 代表枚举类型
在attr.xml中我们可以:
<attr name="mEnum" format="enum">
<enum name="five" value="5"/>
<enum name="six" value="6"/>
<enum name="seven" value="7"/>
</attr>
在我们布局文件中我们可以lgy:mEnum="six"
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
float mFloat = typedArray.getFloat(R.styleable.myTextView_mFloat,0.0f);
Log.i(TAG,"mFloat:" + mFloat);
int mInteger = typedArray.getInteger(R.styleable.myTextView_mInteger,1);
Log.i(TAG,"mInteger:" + mInteger);
int mInt = typedArray.getInt(R.styleable.myTextView_mInteger,2);
Log.i(TAG,"mInt:" + mInt);
String mString = typedArray.getString(R.styleable.myTextView_mString);
Log.i(TAG,"mString:" + mString);
int mEnum = typedArray.getInteger(R.styleable.myTextView_mEnum,5);
Log.i(TAG,"mEnum:" + mEnum);
int mEnumInt = typedArray.getInt(R.styleable.myTextView_mEnum,7);
Log.i(TAG,"mEnumInt:" + mEnumInt);
typedArray.recycle();
运行代码结果图:
7、flag 代表自定义类型
在attr.xml中我们可以:
<attr name="mFlag">
<flag name="one" value="1"/>
<flag name="two" value="2"/>
</attr>
在我们布局文件中我们可以lgy:mFlag="two"
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
float mFloat = typedArray.getFloat(R.styleable.myTextView_mFloat,0.0f);
Log.i(TAG,"mFloat:" + mFloat);
int mInteger = typedArray.getInteger(R.styleable.myTextView_mInteger,1);
Log.i(TAG,"mInteger:" + mInteger);
int mInt = typedArray.getInt(R.styleable.myTextView_mInteger,2);
Log.i(TAG,"mInt:" + mInt);
String mString = typedArray.getString(R.styleable.myTextView_mString);
Log.i(TAG,"mString:" + mString);
int mEnum = typedArray.getInteger(R.styleable.myTextView_mEnum,5);
Log.i(TAG,"mEnum:" + mEnum);
int mEnumInt = typedArray.getInt(R.styleable.myTextView_mEnum,7);
Log.i(TAG,"mEnumInt:" + mEnumInt);
int mFlag = typedArray.getInteger(R.styleable.myTextView_mFlag,1);
Log.i(TAG,"mFlag:" + mFlag);
int mFlagInt = typedArray.getInt(R.styleable.myTextView_mFlag,1);
Log.i(TAG,"mFlagInt:" + mFlagInt);
typedArray.recycle();
运行代码结果图:
8、reference 代表引用 (布局文件、资源文件等)
在attr.xml中我们可以<attr name="mReference" format="reference"/>
在我们布局文件中我们可以lgy:mReference="@android:layout/simple_list_item_1"
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
float mFloat = typedArray.getFloat(R.styleable.myTextView_mFloat,0.0f);
Log.i(TAG,"mFloat:" + mFloat);
int mInteger = typedArray.getInteger(R.styleable.myTextView_mInteger,1);
Log.i(TAG,"mInteger:" + mInteger);
int mInt = typedArray.getInt(R.styleable.myTextView_mInteger,2);
Log.i(TAG,"mInt:" + mInt);
String mString = typedArray.getString(R.styleable.myTextView_mString);
Log.i(TAG,"mString:" + mString);
int mEnum = typedArray.getInteger(R.styleable.myTextView_mEnum,5);
Log.i(TAG,"mEnum:" + mEnum);
int mEnumInt = typedArray.getInt(R.styleable.myTextView_mEnum,7);
Log.i(TAG,"mEnumInt:" + mEnumInt);
int mFlag = typedArray.getInteger(R.styleable.myTextView_mFlag,1);
Log.i(TAG,"mFlag:" + mFlag);
int mFlagInt = typedArray.getInt(R.styleable.myTextView_mFlag,1);
Log.i(TAG,"mFlagInt:" + mFlagInt);
int mReference = typedArray.getResourceId(R.styleable.myTextView_mReference,0);
Log.i(TAG,"mReference:" + mReference);
typedArray.recycle();
运行代码结果图:
9、fraction 代表百分数
在attr.xml中我们可以<attr name="mFraction" format="fraction"/>
在我们布局文件中我们可以lgy:mFractinotallow="30%"
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
float mFloat = typedArray.getFloat(R.styleable.myTextView_mFloat,0.0f);
Log.i(TAG,"mFloat:" + mFloat);
int mInteger = typedArray.getInteger(R.styleable.myTextView_mInteger,1);
Log.i(TAG,"mInteger:" + mInteger);
int mInt = typedArray.getInt(R.styleable.myTextView_mInteger,2);
Log.i(TAG,"mInt:" + mInt);
String mString = typedArray.getString(R.styleable.myTextView_mString);
Log.i(TAG,"mString:" + mString);
int mEnum = typedArray.getInteger(R.styleable.myTextView_mEnum,5);
Log.i(TAG,"mEnum:" + mEnum);
int mEnumInt = typedArray.getInt(R.styleable.myTextView_mEnum,7);
Log.i(TAG,"mEnumInt:" + mEnumInt);
int mFlag = typedArray.getInteger(R.styleable.myTextView_mFlag,1);
Log.i(TAG,"mFlag:" + mFlag);
int mFlagInt = typedArray.getInt(R.styleable.myTextView_mFlag,1);
Log.i(TAG,"mFlagInt:" + mFlagInt);
int mReference = typedArray.getResourceId(R.styleable.myTextView_mReference,0);
Log.i(TAG,"mReference:" + mReference);
float mFraction = typedArray.getFraction(R.styleable.myTextView_mFraction,2,4,0);
Log.i(TAG,"mFraction:" + mFraction);
typedArray.recycle();
运行代码结果图:
**注意:**lgy:mFractinotallow=”30%”
**注意:**lgy:mFractinotallow=”30%p”
注意到我们设置lgy:mFractinotallow=”“值不同得到的结果不一样,那么我们在调用getFraction方法中需要传递4个参数,那么它们分别代表什么呢?为什么得到参数不一样呢?
我们需要查看getFraction源码:
public float getFraction(int index, int base, int pbase, float defValue) {
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_FRACTION) {
return TypedValue.complexToFraction(
data[index+AssetManager.STYLE_DATA], base, pbase);
}
throw new UnsupportedOperationException("Can't convert to fraction: type=0x"
+ Integer.toHexString(type));
}
通过查看源码我可以知道第一个参数和第四参数明白什么意思,第二参数和第三个参数目前我们知道和我们最终返回结果相关,我只有继续往下查看源码:
public static float complexToFraction(int data, float base, float pbase)
{
switch ((data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK) {
case COMPLEX_UNIT_FRACTION:
return complexToFloat(data) * base;
case COMPLEX_UNIT_FRACTION_PARENT:
return complexToFloat(data) * pbase;
}
return 0;
}
哦!通过查看源码我们知道了,当我们设置参数是COMPLEX_UNIT_FRACTION的时候需要*第二个参数base,当我们设置参数是COMPLEX_UNIT_FRACTION_PARENT的时候需要*第三个参数pbase。p代表父容器父布局。
现在我们在看一下我们的例子,当我们设置30%的时候,需要0.3 * 2 = 0.6我们看一下结果是0.599999,当我们设置30%p的时候,需要0.3 * 4 = 1.2我们看一下结果是1.199999,最终返回是一个浮点数。同时我们在看一下COMPLEX_UNIT_FRACTION和COMPLEX_UNIT_FRACTION_PARENT的源码解释就可以明白了。
/** {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall
* size. */
public static final int COMPLEX_UNIT_FRACTION = 0;
/** {@link #TYPE_FRACTION} complex unit: A fraction of the **parent** size. */
public static final int COMPLEX_UNIT_FRACTION_PARENT = 1;
10、dimension 代表尺寸值
在attr.xml中我们可以<attr name="mDimension" format="dimension"/>
在我们布局文件中我们可以lgy:mDimension="32dp"
,
我们在代码中获取我们设置对应值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myTextView);
int mColor = typedArray.getColor(R.styleable.myTextView_mColor, Color.RED);
Log.i(TAG,"mColor:" + mColor);
boolean mBoolean = typedArray.getBoolean(R.styleable.myTextView_mBoolean, false);
Log.i(TAG, "mBoolean:" + mBoolean);
float mFloat = typedArray.getFloat(R.styleable.myTextView_mFloat,0.0f);
Log.i(TAG,"mFloat:" + mFloat);
int mInteger = typedArray.getInteger(R.styleable.myTextView_mInteger,1);
Log.i(TAG,"mInteger:" + mInteger);
int mInt = typedArray.getInt(R.styleable.myTextView_mInteger,2);
Log.i(TAG,"mInt:" + mInt);
String mString = typedArray.getString(R.styleable.myTextView_mString);
Log.i(TAG,"mString:" + mString);
int mEnum = typedArray.getInteger(R.styleable.myTextView_mEnum,5);
Log.i(TAG,"mEnum:" + mEnum);
int mEnumInt = typedArray.getInt(R.styleable.myTextView_mEnum,7);
Log.i(TAG,"mEnumInt:" + mEnumInt);
int mFlag = typedArray.getInteger(R.styleable.myTextView_mFlag,1);
Log.i(TAG,"mFlag:" + mFlag);
int mFlagInt = typedArray.getInt(R.styleable.myTextView_mFlag,1);
Log.i(TAG,"mFlagInt:" + mFlagInt);
int mReference = typedArray.getResourceId(R.styleable.myTextView_mReference,0);
Log.i(TAG,"mReference:" + mReference);
float mFraction = typedArray.getFraction(R.styleable.myTextView_mFraction,2,4,0);
Log.i(TAG,"mFraction:" + mFraction);
float mDimension = typedArray.getDimension(R.styleable.myTextView_mDimension, 14f);
Log.i(TAG, "mDimension:" + mDimension);
typedArray.recycle();
运行代码结果图:
我们设置lgy:mDimensinotallow=”32dp”不同单位得到的值是不相同的。
32dp –>01-03 03:49:23.800 10694-10694/com.lgy.typearray I/MyTextView: mDimension:48.0
32sp –>01-03 03:53:43.210 14638-14638/com.lgy.typearray I/MyTextView: mDimension:48.0
32px –>01-03 03:54:30.430 15446-15446/com.lgy.typearray I/MyTextView: mDimension:32.0
32pt –>01-03 03:57:24.300 18042-18042/com.lgy.typearray I/MyTextView: mDimension:120.41481
32in –>01-03 03:58:07.660 18733-18733/com.lgy.typearray I/MyTextView: mDimension:8669.866
32mm –>01-03 03:59:17.210 19773-19773/com.lgy.typearray I/MyTextView: mDimension:341.3333
为什么设置不同单位得到值不同呢?这也需要查看源码:
public float getDimension(int index, float defValue) {
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(
data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
+ Integer.toHexString(type));
}
此时看不出什么门道来,继续往下看TypedValue.complexToDimension方法
public static float complexToDimension(int data, DisplayMetrics metrics)
{
return applyDimension(
(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
complexToFloat(data),
metrics);
}
继续看applyDimension方法
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
到此豁然开朗了吧!
获取尺寸的方法还有getDimensionPixelSize方法和getDimensionPixelOffset方法。那么它们有什么不同呢?
再一次通过查看源码我就可以得出结论.
public int getDimensionPixelOffset(int index, int defValue) {
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelOffset(
data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
+ Integer.toHexString(type));
}
public int getDimensionPixelSize(int index, int defValue) {
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
+ Integer.toHexString(type));
}
通过源码知道2个方法分别都调用了TypedValue.complexToDimensionPixelOffset,TypedValue.complexToDimensionPixelSize,继续看源码:
public static int complexToDimensionPixelOffset(int data,
DisplayMetrics metrics)
{
return (int)applyDimension(
(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
complexToFloat(data),
metrics);
}
public static int complexToDimensionPixelSize(int data,
DisplayMetrics metrics)
{
final float value = complexToFloat(data);
final float f = applyDimension(
(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
value,
metrics);
final int res = (int)(f+0.5f);
if (res != 0) return res;
if (value == 0) return 0;
if (value > 0) return 1;
return -1;
}
最终2个方法也都调用了applyDimension方法,只不过两者调用了applyDimension方法强制转换int,complexToDimensionPixelSize方法得到值之后又做了简单处理。
那么现在我们可以知道getDimension、getDimensionPixelSize、getDimensionPixelOffset三者区别:
相同点:不同单位尺寸设置得到不同结果。都会调用applyDimension方法。
不同点:getDimension返回float类型,getDimensionPixelSize、getDimensionPixelOffset都是返回int类型。