自定义 View 初探,一个简单的验证码View (一)
2017-08-14 21:13
459 查看
想要学好 android, 除了使用 google 提供好的 view 外,还必须会在 google 提供的框架上会自定义新的 View, 供不同的场合使用。
这里使用一个简单的验证码 View 作为入门选择。
首先,我们明确一下自定义 View 的过程
1. 定义自定义 view 的属性
2. 获取定义的属性
3. 重写 onMeasure()
4. 重写 onDraw()
网络上简单过很多验证码,让我们很无奈,有时候烦不胜烦,但这又有存在的必要性。
这里一步一步来实现一个验证码的 View,一般而言,有下面这些特征:
设置文字
文字大小不一致
文字颜色不一致
干扰圆点
干扰线条
那么根据上面这些特征,我们首先定义 View 的
1. 在 res/values/ 目录下新建 attrs.xml 在里面定义我们的样式
通过这样的一个属性,我们确定了我们需要的一些属性。然后,根据我们的需求,这里选择直接继承 View 来进行实现, 先定义一些所需要的 field并在构造函数中对所需要的值进行初始化
接下来重写 onMeasure, 在 wrap_content 这样的配置时进行一些简单的计算,这里选择将值都 × 2分开这些字符。
在 view 的规格确定了之后,可以开始在 view 内画出 view 的内容
在确定了内容存在后,按顺序画出内容,这里选择按文字,干扰点,干扰线的顺序画出内容。
为了让文字不是正向,这里采用倾斜画布(canvas.rotate)的方式来实现,也可以使用 canvas.drawTextOnPath 的方式来实现。
然后讲干扰点,干扰线画在画布上,这里选择的是使用一个。可能会出现当父view 需要 scroll 或者类似的变化时,颜色发生变化,可以采用初始化一个 List 的 Point 以及 一个 List 的 Path 来实现。
接下来我们需要在 layout_main.xml 中添加本 view 进去。注意,需要 xmlns:app=”http://schemas.android.com/apk/res-auto” 这样的一行内容来引入自定义属性。
下面开始运行:
可以看到,我们的 验证码View 基本实现了,然后就可以根据具体项目要求,对于配置进行自定义调制。
比如说在属性配置中把干扰线,干扰点进行定制处理,增加对应的文字设置事件等。
这里使用一个简单的验证码 View 作为入门选择。
首先,我们明确一下自定义 View 的过程
1. 定义自定义 view 的属性
2. 获取定义的属性
3. 重写 onMeasure()
4. 重写 onDraw()
网络上简单过很多验证码,让我们很无奈,有时候烦不胜烦,但这又有存在的必要性。
这里一步一步来实现一个验证码的 View,一般而言,有下面这些特征:
设置文字
文字大小不一致
文字颜色不一致
干扰圆点
干扰线条
那么根据上面这些特征,我们首先定义 View 的
1. 在 res/values/ 目录下新建 attrs.xml 在里面定义我们的样式
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="mix_color" format="boolean" /> <attr name="text" format="string" /> <attr name="max_text_size" format="dimension" /> <attr name="min_text_size" format="dimension" /> <declare-styleable name="VerificationCodeView"> <!--文字颜色是否不一致--> <attr name="mix_color"/> <!--文字--> <attr name="text"/> <!--文字大小随机范围的小值--> <attr name="max_text_size"/> <!--文字大小随机范围的大值--> <attr name="min_text_size"/> <!-- 文字颜色 --> <attr name="color" format="color" /> </declare-styleable> </resources>
通过这样的一个属性,我们确定了我们需要的一些属性。然后,根据我们的需求,这里选择直接继承 View 来进行实现, 先定义一些所需要的 field并在构造函数中对所需要的值进行初始化
/** * 文字内容 */ private String mText; /** * 文字最大字号 */ private int mMaxTextSize; /** * 文字最小字号 */ private int mMinTextSize; /** * 是否混合颜色 */ private boolean mMixColor; /** * 字的颜色,在非混合颜色时生效 */ private int mColor; /** * 绘制字体时占用的边界 */ private Rect mBounds; /** * 文字画笔 */ private Paint mPaint; /** * 干扰点画笔 */ private Paint mPointPaint; /** * 干扰线画笔 */ private Paint mPathPaint; private Random rnd; private Point point = new Point(); private Path path = new Path(); /** * 单个字符所占宽度 */ private int charWidth; public VerificationCodeView(Context context){ this(context, null); } public VerificationCodeView(Context context, AttributeSet attrs){ this(context, attrs, 0); } public VerificationCodeView(Context context, AttributeSet attrs, int defStyle){ super(context, attrs, defStyle); /* * 获取自定义样式 */ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.VerificationCodeView, defStyle, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.VerificationCodeView_text: mText = a.getString(attr); break; case R.styleable.VerificationCodeView_mix_color: mMixColor = a.getBoolean(attr, true); break; case R.styleable.VerificationCodeView_max_text_size: mMaxTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, DEFAULT_MAX_TEXT_ 4000 SIZE, getResources().getDisplayMetrics())); break; case R.styleable.VerificationCodeView_min_text_size: mMinTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, DEFAULT_MIN_TEXT_SIZE, getResources().getDisplayMetrics())); break; case R.styleable.VerificationCodeView_color: mColor = a.getColor(attr, Color.BLACK); } } a.recycle(); this.mPaint = new Paint(); this.mPointPaint = new Paint(); this.mPathPaint = new Paint(); this.mBounds = new Rect(); this.rnd = new Random(10); if (mText == null){ mPaint.setTextSize(mMaxTextSize); mPaint.getTextBounds(NONE_TEXT_TIP, 0, NONE_TEXT_TIP.length(), mBounds); }else{ mPaint.setTextSize(mMaxTextSize); mPaint.getTextBounds(mText, 0, mText.length(), mBounds); } // 干扰点及干扰线的属性 mPointPaint.setStrokeWidth(5); mPointPaint.setStrokeCap(Paint.Cap.ROUND); mPathPaint.setStrokeWidth(5); mPathPaint.setStyle(Paint.Style.STROKE); mPathPaint.setStrokeCap(Paint.Cap.ROUND); }
接下来重写 onMeasure, 在 wrap_content 这样的配置时进行一些简单的计算,这里选择将值都 × 2分开这些字符。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Log.i(TAG, "onMeasure"); int width; int height; if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY){ width = MeasureSpec.getSize(widthMeasureSpec); }else{ if (mText == null){ width = NONE_TEXT_SIZE; }else{ width = mBounds.width() * 2 + getPaddingLeft() + getPaddingRight(); } } if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY){ height = MeasureSpec.getSize(heightMeasureSpec); }else{ if (mText == null){ height = NONE_TEXT_SIZE; }else{ height = mBounds.height() * 2 + getPaddingBottom() + getPaddingTop(); } } if(mText != null && mText.length() > 0){ charWidth = width / mText.length(); } setMeasuredDimension(width, height); }
在 view 的规格确定了之后,可以开始在 view 内画出 view 的内容
@Override protected void onDraw(Canvas canvas) { if (mText == null){ mPaint.setTextSize(mMinTextSize); mPaint.setAlpha(33); canvas.drawText(NONE_TEXT_TIP, 0, getWidth()/2, mPaint); }else{ for (int i = 0; i < mText.length(); i++){ if (mMixColor){ mPaint.setColor(getNextForegroundColor()); }else{ mPaint.setColor(mColor); } int offsetDegree = 15 - rnd.nextInt(30); canvas.save(); canvas.rotate(offsetDegree, getWidth()/2, getHeight()/2); mPaint.setTextSize(rnd.nextInt(mMaxTextSize-mMinTextSize) + mMinTextSize); canvas.drawText(mText, i, i+1, getPaddingLeft() + charWidth * i + charWidth/2, getHeight() / 2 + mBounds.height() / 2, mPaint); canvas.restore(); } for (int i = 0; i < DEFAULT_POINT_NUM; i++){ point.set(rnd.nextInt(getWidth()), rnd.nextInt(getHeight())); mPointPaint.setColor(getNextForegroundColor()); canvas.drawPoint(point.x, point.y, mPointPaint); } mPathPaint.setColor(getNextForegroundColor()); path.reset(); path.moveTo(0, rnd.nextInt(getHeight())); path.quadTo(getWidth()/2, getHeight()/2, getWidth(), rnd.nextInt(getHeight())); canvas.drawPath(path, mPathPaint); } }
在确定了内容存在后,按顺序画出内容,这里选择按文字,干扰点,干扰线的顺序画出内容。
为了让文字不是正向,这里采用倾斜画布(canvas.rotate)的方式来实现,也可以使用 canvas.drawTextOnPath 的方式来实现。
然后讲干扰点,干扰线画在画布上,这里选择的是使用一个。可能会出现当父view 需要 scroll 或者类似的变化时,颜色发生变化,可以采用初始化一个 List 的 Point 以及 一个 List 的 Path 来实现。
接下来我们需要在 layout_main.xml 中添加本 view 进去。注意,需要 xmlns:app=”http://schemas.android.com/apk/res-auto” 这样的一行内容来引入自定义属性。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="me.leo.view.MainActivity"> <me.leo.view.widget.VerificationCodeView android:id="@+id/verification_1" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/colorPrimaryDark" app:mix_color="true" app:max_text_size="28sp" app:min_text_size="20sp" app:text="@string/app_name"/> <me.leo.view.widget.VerificationCodeView android:id="@+id/verification_2" android:layout_marginTop="10dp" android:layout_below="@id/verification_1" android:layout_centerHorizontal="true" android:layout_width="300dp" android:layout_height="200dp" android:background="@color/colorPrimaryDark" app:mix_color="false" app:max_text_size="28sp" app:min_text_size="20sp" app:color="@color/colorAccent" app:text="@string/app_name"/> </RelativeLayout>
下面开始运行:
可以看到,我们的 验证码View 基本实现了,然后就可以根据具体项目要求,对于配置进行自定义调制。
比如说在属性配置中把干扰线,干扰点进行定制处理,增加对应的文字设置事件等。
相关文章推荐
- 自定义一个view,并实现最简单的手势识别功能(上)
- 一个炫酷的仿雷达扫描和扩散效果——自定义View就是这么简单
- 实作一个简单自定义的View(一) -- A Simple Custom View Example (1)
- 自定义TextView简单几步制作一个展示消息的滚动条
- 自定义View实践-一个简单的棋类游戏
- Android下 一个自定义VIEW实现简单的弹幕效果
- Android自定义View 做个简单的验证码控件
- Android 实现一个简单的自定义View
- android 自定义一个简单View总结
- 自定义View实现简单的数字验证码(一)
- 自定义 View 实战(一)做一个简单的进度条
- 自定义View学习-绘制一个简单的圆
- Android自定义View入门之简单验证码控件
- 自定义一个简单的可以加载网络图片的ImageView
- Android自定义View:你需要一个简单好用、含历史搜索记录的搜索框吗?
- 自定义View探究-一个简单的垂直上拉下滑View
- 一个简单的Android自定义view详解
- 创建一个简单的表视图&自定义UITableView的表单元格
- 一个简单的collectionView自定义布局
- 一个简单的自定义View 大风车