完整步骤
自定义View的步骤: 1、在res/values中自定义View的属性2、在构造方法中遍历我们自定义的属性,并根据这些属性值对成员变量初始化3、重写onMesure,测量view的宽高,view视图大小的将在这里最终确定,子类可以覆写onMeasure()方法实现自己的计算视图大小的方式,并最终通过setMeasuredDimension(width, height)保存计算结果。4、重写onLayout(),确定view的位置。该函数主要是为ViewGroup类型布局子视图用的,自定义View时可以不重写此方法。5、重写onDraw,绘制view的内容,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。对于ViewGroup则不需要实现该函数,因为容器本身是“没有内容“的,其包含的子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是dispatchDraw()方法。6、添加事件监听、回调方法
自定义属性支持的类型
reference:参考某一资源ID, = "@drawable/图片ID" color:颜色值, = "#00FF00"(不能引用colors中定义的值,若要引用请使用reference) boolean:布尔值, = "true" dimension:尺寸值, = "42dp" float:浮点值, = "0.7" integer:整型值, = "100" string:字符串, = "string" fraction:百分数, = "200%" enum:枚举值, <attr name="orientation"> <enum name="vertical" value="1" />, = "vertical" flag:位"或", <attr name="windowSoftInputMode"> <flag name = "stateUnspecified" value= "0" /> <flag name = "stateUnchanged" value= "1" />, = "stateUnspecified | stateUnchanged"> 属性定义时可以指定多种类型值, <attr name = "background" format= "reference | color" />, = "@drawable/图片ID 或 #00FF00"
代码中获取设置的值:
重写onMeasure及三种测量模式
onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法 1、onMeasure()函数由包含这个View的具体的ViewGroup调用,因此两个参数widthMeasureSpec, heightMeasureSpec也是从这个ViewGroup中传入的。 2、这两个参数是由ViewGroup中的layout_width、layout_height、padding,以及View自身的layout_margin、layout_width、layout_height、padding等共同决定的。另外,权值weight也是需要考虑的因素。 3、这两个值的作用 如对于heightMeasureSpec,这个值由高32位和低16位组成,高32位保存的值叫specMode,可以通过MeasureSpec.getMode()获取;低16位为specSize,可以由MeasureSpec.getSize()获取。 那么specMode和specSize的作用又是什么呢?要想知道这一点,我们需要知道代码中的最后一行,所有的View的onMeasure()的最后一行都会调用setMeasureDimension()函数,它的的作用是:根据传进去的值确定View最终的视图大小。也就是说onMeasure()中之前所作的所有工作都是为了最后这一句话服务的。
MeasureSpec.specMode的三种类型: 在ViewGroup中,给View分配的空间大小并不是确定的,有可能随着具体的变化而变化,而这个变化的条件就是传到specMode中决定的 1、EXACTLY(精确):指设置了明确的值(如android:layout_width="100dp"),其中包括MATCH_PARENT(因为这时值也是确定的)。这种模式下,由于我们已明确设置了view的宽度和高度,所以系统帮我们测量的结果就是我们要设置的结果,所以我们可以直接使用系统测量的值。 2、AT_MOST(最大):表示子布局限制在一个最大值内,而在此范围内的具体大小并不确定,一般为WARP_CONTENT。在这种模式下,由于我们没有明确设置view的宽度和高度,具体的大小是可能变化的,所以系统帮我们测量的结果只是我们能设置的结果的最大允许值,所以我们不能直接使用系统测量的值,而要自己根据自己的需要手动设置大小。 3、UNSPECIFIED:表示子布局想要多大就多大,基本使用不到。
注意:RelativeLayout 是一个比较复杂的 ViewGroup,其中子 view 的大小不仅跟 layout_width、layout_height 属性相关,还和很多其他属性有关系(如align、toRight等),若自定义的view重写了onMeasure方法,但并没有处理这些情况下对View大小的影响,则在RelativeLayout中使用时可能会出现一些尺寸和我们预期的不一致的问题
【View】
public class MyTitleView extends View implements OnClickListener { /**初始文本内容,在定义时赋初值,当在布局中没有定义时就是用默认值。注意:由于字体大小可能不是相等的,而这里初始化的值决定了文本框的大小,所以这里的初始值应该设置占用空间比较大的字符。这里以后可以再优化*/ private String mTitleText = "8888"; /**文本的颜色*/ private int mTitleTextColor = Color.RED; /**文本字体大小*/ private int mTitleTextSize; /**背景色*/ private int mTitleBackgroundColor = Color.YELLOW; /**圆角大小*/ private int mTitleRoundSize; /**绘制时文本绘制的范围*/ private Rect mRect; private Paint mPaint;//画笔 private Context mContext; //在【代码】里面创建对象的时候使用此构造方法 public MyTitleView(Context context) { this(context, null); } //在【布局】文件中使用时,系统默认会调用两个参数的构造方法 public MyTitleView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyTitleView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; initAttrs(context, attrs, defStyle); initRect(); mTitleRoundSize = dp2px(5); setOnClickListener(this); } //初始化属性集 private void initAttrs(Context context, AttributeSet attrs, int defStyle) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTitleView, defStyle, 0);//获取我们在【属性集】中【定义】的自定义属性的集合 int count = typedArray.getIndexCount();//获取我们在【布局】文件中【设置】的自定义属性的个数,若没有设置任何自定义的属性,则此值为0 for (int i = 0; i < count; i++) { int attrIndex = typedArray.getIndex(i);//获取此属性的编号,此值是由R文件自动生成的,代表【属性集】中定义的属性的位置,第一个属性对应的编号为0 switch (attrIndex) { case R.styleable.MyTitleView_titleText://=0 mTitleText = typedArray.getString(attrIndex);// 获取【布局】文件中设置的值 break; case R.styleable.MyTitleView_titleTextColor://=1 mTitleTextColor = typedArray.getColor(attrIndex, Color.RED);//defValue:Value to return if the attribute is not defined or not a resource // 注意:布局中没设置此属性时代码根本执行不到这里,所以不能在此设置默认值,默认值建议直接在构造方法中初始化。 break; case R.styleable.MyTitleView_titleBackground://=2 mTitleBackgroundColor = typedArray.getColor(attrIndex, Color.YELLOW); break; case R.styleable.MyTitleView_titleTextSize://=3 mTitleTextSize = typedArray.getDimensionPixelSize(attrIndex, 30);//能识别初代码中单位是sp(或dp)还是px break; } } typedArray.recycle();//Give back a previously retrieved array, for later re-use. } //获取文本需要占用的空间大小 private void initRect() { mPaint = new Paint(); //如果有自定义大小,就用自定义的,否则设置一个默认的。因为定义成员时Context还不存在,所以放在这里赋初值。 if (mTitleTextSize == 0) mTitleTextSize = dp2px(30); mPaint.setTextSize(mTitleTextSize);//单位为px mRect = new Rect(); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mRect);//将初始文本的边界值封装到矩形mRect中 } @Override public void onClick(View v) { mTitleText = getRandomText(); invalidate();//调用此方法主动刷新UI。If the view is visible, onDraw will be called at some point in the future。 } @Override //测量view尺寸时的回调方法 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //设置宽度 int width = 0; int width_size = MeasureSpec.getSize(widthMeasureSpec);//测量的值 int width_mode = MeasureSpec.getMode(widthMeasureSpec);//测量模式 switch (width_mode) { case MeasureSpec.EXACTLY:// 设置了具体的值或者是【MATCH_PARENT】时,可以直接使用测量的值 width = getPaddingLeft() + getPaddingRight() + width_size;//Padding值+测量值 break; case MeasureSpec.AT_MOST:// 当我们设置为【WARP_CONTENT】时,测量的值实际为【MATCH_PARENT】,不能使用,所以我们重新计算 width = getPaddingLeft() + getPaddingRight() + mRect.width();//Padding值+文本实际占用空间 break; } //设置高度 int height = 0; int height_size = MeasureSpec.getSize(heightMeasureSpec); int height_mode = MeasureSpec.getMode(heightMeasureSpec); switch (height_mode) { case MeasureSpec.EXACTLY: height = getPaddingTop() + getPaddingBottom() + height_size; break; case MeasureSpec.AT_MOST: height = getPaddingTop() + getPaddingBottom() + mRect.height(); break; } //设置控件实际大小 setMeasuredDimension(width, height); } @Override //在onDraw中绘制【圆角矩形】的背景和随机生成的文字。只要图形稍有改变,此方法在就会调用 protected void onDraw(Canvas canvas) { Log.i("bqt", "getMeasuredWidth()=" + getMeasuredWidth() + ",getMeasuredHeight()=" + getMeasuredHeight());//点击一次调用一次 mPaint.setColor(mTitleBackgroundColor); canvas.drawRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), mTitleRoundSize, mTitleRoundSize, mPaint);//绘制背景 mPaint.setColor(mTitleTextColor); canvas.drawText(mTitleText, getWidth() / 2 - mRect.width() / 2, getHeight() / 2 + mRect.height() / 2, mPaint);//居中绘制文字 } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public int dp2px(float dpValue) { final float scale = mContext.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } /** * 获取一个四位数字的随机数 */ public String getRandomText() { Random random = new Random(); Set<Integer> set = new HashSet<Integer>(); while (set.size() < 4) { int randomInt = random.nextInt(10); set.add(randomInt); } StringBuffer sb = new StringBuffer(); for (Integer i : set) { sb.append("" + i); } return sb.toString(); } }
使用
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bqt="http://schemas.android.com/apk/res/com.bqt.myview" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" > <com.bqt.myview.MyTitleView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" /> <com.bqt.myview.MyTitleView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:paddingBottom="5dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:paddingTop="5dp" bqt:titleBackground="#3f00" bqt:titleText="8888" bqt:titleTextColor="#0f0" bqt:titleTextSize="25sp" /> <com.bqt.myview.MyTitleView android:layout_width="150dp" android:layout_height="50dp" android:layout_marginTop="10dp" bqt:titleBackground="#30f0" bqt:titleText="8888" bqt:titleTextColor="#00f" bqt:titleTextSize="35sp" /> <com.bqt.myview.MyTitleView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" bqt:titleBackground="#300f" bqt:titleText="8888" bqt:titleTextColor="#f0f" bqt:titleTextSize="50sp" /> </LinearLayout>
自定义属性
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- 注意,自定义属性定义好后顺序若变了必须clean一下工程好让R文件同步更新,否则运行时就会挂掉 --> <declare-styleable name="MyTitleView"> <attr name="titleText" format="string" /> <attr name="titleTextColor" format="color" /> <attr name="titleBackground" format="color" /> <attr name="titleTextSize" format="dimension" /> </declare-styleable> </resources>
时间: 2024-08-02 02:46:32