[关闭]
@wangwangheng 2014-11-16T14:15:02.000000Z 字数 4744 阅读 1962

自定义View

自定义View


本文转载自 Android 自定义View (一),Android 自定义View (二) 进阶并且进行了整理

自定义View的一般步骤:

  • 自定义View的属性
  • 在View的构造方法中获得我们的自定义属性
  • [重写onMeasure方法]
  • 重写onDraw()

第3步并不是必须的

首先在res/values/下创建一个attrs.xml文件,在里面定义自定义的属性和声明样式:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <attr name="titleText" format="string" />
  4. <attr name="titleTextColor" format="color" />
  5. <attr name="titleTextSize" format="dimension" />
  6. <declare-styleable name="CustomTitleView">
  7. <attr name="titleText" />
  8. <attr name="titleTextColor" />
  9. <attr name="titleTextSize" />
  10. </declare-styleable>
  11. </resources>

上面的format的具体取值请参考自定义View的declare-styleable中format详解

然后在layout文件中声明我们自定义的View:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent" >
  6. <com.example.customview01.view.CustomTitleView
  7. android:layout_width="200dp"
  8. android:layout_height="100dp"
  9. custom:titleText="3712"
  10. custom:titleTextColor="#ff0000"
  11. custom:titleTextSize="40sp" />
  12. </RelativeLayout>

RelativeLayout中的xmls:custome命名空间的名字可以是任何合法的标识符,命名空间的最后是程序的包名,为了适应不同的包名,我们一般使用下面的字符串替换命名空间的内容:http://schemas.android.com/apk/res-auto

  1. /**
  2. * 文本
  3. */
  4. private String mTitleText;
  5. /**
  6. * 文本的颜色
  7. */
  8. private int mTitleTextColor;
  9. /**
  10. * 文本的大小
  11. */
  12. private int mTitleTextSize;
  13. /**
  14. * 绘制时控制文本绘制的范围
  15. */
  16. private Rect mBound;
  17. private Paint mPaint;
  18. public CustomTitleView(Context context, AttributeSet attrs){
  19. this(context, attrs, 0);
  20. }
  21. public CustomTitleView(Context context){
  22. this(context, null);
  23. }
  24. /**
  25. * 获得我自定义的样式属性
  26. *
  27. * @param context
  28. * @param attrs
  29. * @param defStyle
  30. */
  31. public CustomTitleView(Context context, AttributeSet attrs, int defStyle){
  32. super(context, attrs, defStyle);
  33. /**
  34. * 获得我们所定义的自定义样式属性
  35. */
  36. TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
  37. int n = a.getIndexCount();
  38. for (int i = 0; i < n; i++){
  39. int attr = a.getIndex(i);
  40. switch (attr){
  41. case R.styleable.CustomTitleView_titleText:
  42. mTitleText = a.getString(attr);
  43. break;
  44. case R.styleable.CustomTitleView_titleTextColor:
  45. // 默认颜色设置为黑色
  46. mTitleTextColor = a.getColor(attr, Color.BLACK);
  47. break;
  48. case R.styleable.CustomTitleView_titleTextSize:
  49. // 默认设置为16sp,TypeValue也可以把sp转化为px
  50. mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
  51. TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
  52. break;
  53. }
  54. }
  55. a.recycle();
  56. /**
  57. * 获得绘制文本的宽和高
  58. */
  59. mPaint = new Paint();
  60. mPaint.setTextSize(mTitleTextSize);
  61. // mPaint.setColor(mTitleTextColor);
  62. mBound = new Rect();
  63. mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
  64. }

注意,TypeArray在使用完成之后一定要调用它的recycle()方法

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
  3. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  4. }
  5. @Override
  6. protected void onDraw(Canvas canvas){
  7. mPaint.setColor(Color.YELLOW);
  8. canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
  9. mPaint.setColor(mTitleTextColor);
  10. canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
  11. }

此时运行程序,可以看到如下效果:
图片

但是当我们把View的属性从一个具体的大小设置为wrap_content之后,效果图变成下面这个样子了:wrap_content效果

系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。

所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法:

重写之前先了解MeasureSpec的specMode,一共三种类型:

EXACTLY:一般是设置了明确的值或者是MATCH_PARENT

AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

UNSPECIFIED:表示子布局想要多大就多大,很少使用

下面是我们重写onMeasure代码:

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
  3. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  4. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  5. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  6. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  7. int width;
  8. int height ;
  9. if (widthMode == MeasureSpec.EXACTLY){
  10. width = widthSize;
  11. } else{
  12. mPaint.setTextSize(mTitleTextSize);
  13. mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
  14. float textWidth = mBounds.width();
  15. int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
  16. width = desired;
  17. }
  18. if (heightMode == MeasureSpec.EXACTLY){
  19. height = heightSize;
  20. } else{
  21. mPaint.setTextSize(mTitleTextSize);
  22. mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
  23. float textHeight = mBounds.height();
  24. int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
  25. height = desired;
  26. }
  27. setMeasuredDimension(width, height);
  28. }

现在我们修改下布局文件:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent" >
  6. <com.example.customview01.view.CustomTitleView
  7. android:layout_width="wrap_content"
  8. android:layout_height="wrap_content"
  9. custom:titleText="3712"
  10. android:padding="10dp"
  11. custom:titleTextColor="#ff0000"
  12. android:layout_centerInParent="true"
  13. custom:titleTextSize="40sp" />
  14. </RelativeLayout>

现在的效果是:重写onMeasure后

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注