[关闭]
@ZeroGeek 2015-08-19T06:13:45.000000Z 字数 10212 阅读 680

从Zero开始自定义View (三 )

view android


http://developer.android.com/intl/zh-cn/reference/android/graphics/Canvas.html
http://developer.android.com/intl/zh-cn/reference/android/graphics/Paint.html

结合官网详细学习一遍Canvas和Paint的用法,再做一个验证码生成器

1. Paint

1.1 简介

提供绘制几何图形,文本,位图的样式和颜色。

1.2 构造方法

  • Paint()
  • Paint(int flags) flags涉及一些图形学概念,如锯齿,抖动,平滑等绘制效果
  • Paint(Paint paint)

通常我们使用第一种就好

1.3 公有方法

重头戏(这里我没一一列举,列举一些常用的,详细自行查阅)

  • void clearShadowLayer() //清除阴影层
  • int getAlpha()
  • int getColor()
  • boolean getFillPath(Path src, Path dst)
  • int getFlags()
  • int getHinting()
  • PathEffect getPathEffect()
  • Paint.Style getStyle()
  • Paint.Align getTextAlign()
  • void getTextBounds(char[] text, int index, int count, Rect bounds)
  • void getTextBounds(String text, int start, int end, Rect bounds)
  • void getTextPath(char[] text, int index, int count, float x, float y, Path path)
  • int getTextWidths(String text, int start, int end, float[] widths)
  • void reset()
  • void set(Paint src)
  • void setARGB(int a, int r, int g, int b)
  • void setAlpha(int a)
  • void setColor(int color)
  • void setFlags(int flags)
  • void setHinting(int mode)
  • void setStyle(Paint.Style style)

Canvas

1.1 简介

1.2 构造方法

1.3 共有常用方法

看着看着就不想翻译了=。= ! 建议先去复习下计算机图形学...

构造函数说明

参考 : http://www.cnblogs.com/angeldevil/p/3479431.html

如果在Code中实例化一个View会调用第一个构造函数,如果在xml中定义会调用第二个构造函数,而第三个函数系统是不调用的,要由View显式调用,比如我们可以在第二个构造函数中调用了第三个构造函数。
第三个参数的意义就如同它的名字所说的,是默认的Style,只是这里没有说清楚,这里的默认的Style是指它在当前Application或Activity所用的Theme中的默认Style。

建议分析TextView的源码,得出自定义View的一些技巧或方法

做个验证码生成器

参考:http://blog.csdn.net/lmj623565791/article/details/24252901

首先贴出核心的代码,重要地方都标有注释:

  1. package com.zero.apptest;
  2. import android.content.Context;
  3. import android.content.res.TypedArray;
  4. import android.graphics.Canvas;
  5. import android.graphics.Color;
  6. import android.graphics.Paint;
  7. import android.graphics.Point;
  8. import android.graphics.Rect;
  9. import android.util.AttributeSet;
  10. import android.util.Log;
  11. import android.util.TypedValue;
  12. import android.view.View;
  13. import java.util.ArrayList;
  14. import java.util.List;
  15. /**
  16. * 生成数字验证码
  17. * Created by zero on 15-8-18.
  18. */
  19. public class RandomView extends View implements View.OnClickListener{
  20. private static final String TAG = "RandomView";
  21. private String mTitleText; //验证码内容
  22. private int mTitleTextColor; //验证码数字颜色
  23. private int mTitleTextSize; //数字大小
  24. private Rect mBound; //内容大小
  25. private Paint mPaint;
  26. public static final int LEVEL_NO = 0;
  27. public static final int LEVEL_ONE = 1;
  28. public static final int LEVEL_TWO = 2;
  29. private int mNumber; //验证码数字个数
  30. private int mLevel; //识别度
  31. public RandomView(Context context,AttributeSet attrs) {
  32. this(context, attrs, 0);
  33. }
  34. public RandomView(Context context) {
  35. this(context,null);
  36. }
  37. /**
  38. * 获得自定义样式属性
  39. */
  40. public RandomView(Context context, AttributeSet attrs, int defStyleAttr) {
  41. super(context, attrs, defStyleAttr);
  42. //设置默认属性
  43. mNumber = 4;
  44. mLevel = LEVEL_ONE;
  45. //读取自定义属性
  46. TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,R.styleable.RandomView, defStyleAttr, 0);
  47. int n = typedArray.getIndexCount();
  48. //遍历所有属性
  49. for (int i = 0; i < n; i++) {
  50. int attr = typedArray.getIndex(i);
  51. switch (attr) {
  52. case R.styleable.RandomView_myText:
  53. mTitleText = typedArray.getString(attr);
  54. break;
  55. case R.styleable.RandomView_myTextColor:
  56. mTitleTextColor = typedArray.getColor(attr, Color.BLACK); // 设置默认颜色
  57. break;
  58. case R.styleable.RandomView_myTextSize:
  59. //默认设置为16sp,TypeValue可以把sp转化为px
  60. mTitleTextSize = typedArray.getDimensionPixelSize(attr,
  61. (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,16,getResources().getDisplayMetrics()));
  62. break;
  63. }
  64. }
  65. typedArray.recycle(); //需要回收
  66. mPaint = new Paint();
  67. mPaint.setTextSize(mTitleTextSize);
  68. mBound = new Rect();
  69. mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound);
  70. setOnClickListener(this); //注意绑定监听点击事件
  71. }
  72. @Override
  73. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  74. //分宽,高来测量,先学习三种模式MeasureSpec.EXACTLY,MeasureSpec.AT_MOST,MeasureSpec.UNSPECIFIED
  75. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  76. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  77. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  78. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  79. int width;
  80. int height ;
  81. if (widthMode == MeasureSpec.EXACTLY) {
  82. width = widthSize;
  83. } else {
  84. mPaint.setTextSize(mTitleTextSize);
  85. mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
  86. float textWidth = mBound.width();
  87. int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
  88. width = desired;
  89. }
  90. if (heightMode == MeasureSpec.EXACTLY) {
  91. height = heightSize;
  92. } else {
  93. mPaint.setTextSize(mTitleTextSize);
  94. mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
  95. float textHeight = mBound.height();
  96. int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
  97. height = desired;
  98. }
  99. //注意必须设置
  100. setMeasuredDimension(width, height);
  101. }
  102. @Override
  103. protected void onDraw(Canvas canvas) {
  104. super.onDraw(canvas);
  105. final float textWith = mBound.width();
  106. final float textHeight = mBound.height();
  107. final int width = getMeasuredWidth();
  108. final int height = getMeasuredHeight();
  109. mPaint.setColor(Color.parseColor("#BDC3C7"));
  110. canvas.drawRect(0, 0, width, height, mPaint);
  111. mPaint.setColor(mTitleTextColor);
  112. canvas.drawText(mTitleText, width / 2 - textWith / 2, height / 2 + textHeight / 2, mPaint);
  113. switch (mLevel) {
  114. case LEVEL_NO:
  115. break;
  116. case LEVEL_ONE:
  117. paintComplex(20,20,width,height,canvas);
  118. break;
  119. case LEVEL_TWO:
  120. paintComplex(40,40,width,height,canvas);
  121. break;
  122. default:
  123. break;
  124. }
  125. }
  126. @Override
  127. public void onClick(View v) {
  128. mTitleText = getRandomNumbers(mNumber);
  129. requestLayout(); //请求重新设置布局调用onMeasure()
  130. invalidate(); //请求重绘,调用onDraw()
  131. }
  132. public void setNumber(int number) {
  133. if (number > 0 && number < 10) {
  134. mNumber = number;
  135. }
  136. }
  137. /**
  138. * 生成n位的数字串
  139. * @param n
  140. * @return
  141. */
  142. private String getRandomNumbers(int n) {
  143. String str = " ";
  144. for (int i = 0; i < n; i++) {
  145. str += (int)(Math.random()*10);
  146. }
  147. Log.d(TAG,"str="+str);
  148. return str.trim();
  149. }
  150. //得到小于10的随机正整数
  151. private int getRandomTen() {
  152. return (int)(Math.random()*10);
  153. }
  154. /**
  155. * 设置验证码识别度
  156. * @param level
  157. */
  158. public void setLevel(int level) {
  159. if (level == LEVEL_NO) {
  160. mLevel = LEVEL_NO;
  161. } else if (level == LEVEL_ONE) {
  162. mLevel = LEVEL_ONE;
  163. } else if (level == LEVEL_TWO) {
  164. mLevel = LEVEL_TWO;
  165. } else {
  166. mLevel = LEVEL_ONE; //默认样式
  167. }
  168. }
  169. /**
  170. *获得在宽w,高h矩形中随机n个点
  171. */
  172. private List<Point> getRandomPoint(int n, int w, int h) {
  173. List<Point> list = new ArrayList<>();
  174. for (int i = 0; i < n; i++ ) {
  175. int x = (int)(Math.random()*1000) % w;
  176. int y = (int)(Math.random()*1000) % h;
  177. Point point = new Point(x,y);
  178. list.add(point);
  179. }
  180. return list;
  181. }
  182. /**
  183. * 画上一些散落的圆点和线段
  184. */
  185. private void paintComplex (int circleTotal,int lineTotal,int width,int height,Canvas canvas) {
  186. List<Point> circles= getRandomPoint(circleTotal,width,height); //得到15个圆心
  187. mPaint.setColor(Color.parseColor("#34495E"));
  188. for (int i = 0 ; i < circles.size() ; i++) {
  189. canvas.drawCircle(circles.get(i).x,circles.get(i).y,4,mPaint);
  190. }
  191. List<Point> lines= getRandomPoint(lineTotal,width,height); //得到10个点,作为弧的左顶点
  192. mPaint.setColor(Color.GREEN);
  193. for (int i = 0 ; i < lines.size() ; i++) {
  194. int x = lines.get(i).x;
  195. int y = lines.get(i).y;
  196. canvas.drawLine(x,y,x+getRandomTen(),y+getRandomTen(),mPaint);
  197. }
  198. }
  199. }

在res/value/下创建attrs.xml,设置自定义属性:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="RandomView" >
  4. <attr name="myText" format="string" />
  5. <attr name="myTextColor" format="color" />
  6. <attr name="myTextSize" format="dimension" />
  7. </declare-styleable>
  8. </resources>

main.xml :
注意 引入 xmlns:test="http://schemas.android.com/apk/res-auto" (在gradle构建下),
如果是在eclipse则引入xmlns:test="http://schemas.android.com/apk/res/<包名>"
其中 ‘test’ ,是自己取个名字,与下面定义保持一致就行。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:test="http://schemas.android.com/apk/res-auto"
  4. xmlns:android="http://schemas.android.com/apk/res/android"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:orientation="vertical"
  8. >
  9. <android.support.design.widget.TextInputLayout
  10. android:id="@+id/textInput"
  11. android:layout_width="match_parent"
  12. android:layout_height="wrap_content">
  13. <EditText
  14. android:layout_width="match_parent"
  15. android:layout_height="wrap_content"
  16. android:textColor="@android:color/black"/>
  17. </android.support.design.widget.TextInputLayout>
  18. <Spinner
  19. android:id="@+id/spinner"
  20. android:layout_width="match_parent"
  21. android:layout_height="80dp">
  22. </Spinner>
  23. <Button
  24. android:id="@+id/btn"
  25. android:layout_width="match_parent"
  26. android:layout_height="wrap_content"
  27. android:text="Go"/>
  28. <com.zero.apptest.RandomView
  29. android:id="@+id/random"
  30. android:layout_width="wrap_content"
  31. android:layout_height="50dp"
  32. test:myText="6890"
  33. test:myTextColor="#ECF0F1"
  34. test:myTextSize="26sp"
  35. android:layout_gravity="center"
  36. android:paddingLeft="5dp"
  37. android:paddingRight="5dp"
  38. />
  39. </LinearLayout>

然后是MainActivity中,主要代码

  1. RandomView mRandomView;
  2. TextInputLayout mTIL;
  3. EditText mEt;
  4. Button mBtn;
  5. Spinner mSpinner;
  6. int mLevel;
  7. private static final String[] mRes={"无效果","等级1","等级2"};
  8. @Override
  9. protected void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.main);
  12. mLevel = RandomView.LEVEL_ONE;
  13. mRandomView = (RandomView) findViewById(R.id.random);
  14. mTIL = (TextInputLayout) findViewById(R.id.textInput);
  15. mBtn = (Button) findViewById(R.id.btn);
  16. mSpinner = (Spinner) findViewById(R.id.spinner);
  17. mTIL.setHint("请输入验证码个数:");
  18. mEt = mTIL.getEditText();
  19. mEt.addTextChangedListener(new TextWatcher() {
  20. @Override
  21. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  22. }
  23. @Override
  24. public void onTextChanged(CharSequence s, int start, int before, int count) {
  25. if (!isNumeric(s.toString())) {
  26. mTIL.setErrorEnabled(true);
  27. mTIL.setError("只能输入数字");
  28. mBtn.setEnabled(false);
  29. } else {
  30. mTIL.setErrorEnabled(false);
  31. mBtn.setEnabled(true);
  32. }
  33. }
  34. @Override
  35. public void afterTextChanged(Editable s) {
  36. }
  37. });
  38. ArrayAdapter<String> adapter = new ArrayAdapter<>(this,android.R.layout.simple_spinner_item,mRes);
  39. adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  40. mSpinner.setAdapter(adapter);
  41. mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
  42. @Override
  43. public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
  44. switch (position) {
  45. case 0:
  46. mLevel = RandomView.LEVEL_NO;
  47. break;
  48. case 1:
  49. mLevel = RandomView.LEVEL_ONE;
  50. break;
  51. case 2:
  52. mLevel = RandomView.LEVEL_TWO;
  53. break;
  54. }
  55. }
  56. @Override
  57. public void onNothingSelected(AdapterView<?> parent) {
  58. }
  59. });
  60. mBtn.setOnClickListener(new View.OnClickListener() {
  61. @Override
  62. public void onClick(View v) {
  63. String str = mEt.getText().toString().trim();
  64. if (!str.equals(null) && !str.equals("")) {
  65. int n = Integer.parseInt(str);
  66. if (n > 0 && n < 10) {
  67. mRandomView.setNumber(n);
  68. mRandomView.setLevel(mLevel);
  69. Toast.makeText(getApplicationContext(),"设置成功",Toast.LENGTH_SHORT).show();
  70. } else {
  71. closeInput();
  72. final Snackbar snackbar = Snackbar.make(mTIL,"不能超过10",Snackbar.LENGTH_LONG);
  73. snackbar.show();
  74. snackbar.setDuration(2000);
  75. snackbar.setAction("取消",new View.OnClickListener() {
  76. @Override
  77. public void onClick(View v) {
  78. snackbar.dismiss();
  79. }
  80. });
  81. }
  82. }
  83. }
  84. });
  85. }
  86. private boolean isNumeric(String str) {
  87. Pattern pattern = Pattern.compile("[0-9]*");
  88. return pattern.matcher(str).matches();
  89. }
  90. private void closeInput() {
  91. InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
  92. imm.hideSoftInputFromWindow(mTIL.getWindowToken(), 0);
  93. }

运行效果:
效果

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