总结-自定义View
来源:互联网 发布:癌症临床试验数据研究 编辑:程序博客网 时间:2024/06/01 07:26
接触android已经挺长时间了,却不是很习惯总结东西,总觉得网上已经有了,自己没必要再去写一些重复的难容,高深的自己都没搞明白也没法写。=一直觉得自己的基础掌握的不牢靠,很多细节性的东西慢慢就忘了,很是发愁。因此还是打算总结一些东西,方便以后查看。先从自定义View开始。
1. View的生命周期
一直以来自定义View在我心里就两个步骤。
1.继承View
2.实现其中的方法
从来没有想过它的生命周期,直到。。。看到了一份面试题,让写出View的声明周期,然后才猛然醒悟,原来View也是有生命周期的啊!不只是原来硬记下来的onMeasure(),onDraw()等..
生命周期顾名思义就是从有到无的过程,我们自定义一个LifeCircleView继承自View,重写其中的一些常用方法,观察一下它的执行流程
package com.hank.ok.view;import android.content.Context;import android.graphics.Canvas;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.util.Log;import android.view.View;/** * 类功能描述: * version:${version} */public class LifeCircleView extends View { public LifeCircleView(Context context) { this(context,null); } public LifeCircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); Log.i("LifeCircleView","--------->构造方法"); } @Override protected void onFinishInflate() { Log.i("LifeCircleView","--------->onFinishInflate"); super.onFinishInflate(); } @Override protected void onAttachedToWindow() { Log.i("LifeCircleView","--------->onAttachedToWindow"); super.onAttachedToWindow(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Log.i("LifeCircleView","--------->onMeasure"); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { Log.i("LifeCircleView","--------->onSizeChanged"); super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onDraw(Canvas canvas) { Log.i("LifeCircleView","--------->onDraw"); super.onDraw(canvas); } @Override protected void onDetachedFromWindow() { Log.i("LifeCircleView","--------->onDetachedFromWindow"); super.onDetachedFromWindow(); }}
打印的结果如下:
I/LifeCircleView: --------->构造方法I/LifeCircleView: --------->onFinishInflateI/LifeCircleView: --------->onAttachedToWindowI/LifeCircleView: --------->onMeasureI/LifeCircleView: --------->onMeasureI/LifeCircleView: --------->onSizeChangedI/LifeCircleView: --------->onDrawI/LifeCircleView: --------->onMeasureI/LifeCircleView: --------->onDraw
如果此时用户按了返回键,就会执行onDetachedFromWindow方法
所以从打印的结果我们很容易得出View的执行流程
构造方法->onFinishInflate->onAttachedToWindow->onMeasure->onSizeChange->onDraw->onDetachedFromWindow
所以也就明白了为什么可以在onSizeChanged()方法中获取到View的宽和高,因为这个执行已经测量过了。
2. View的坐标系
如果对坐标系都搞不清楚,就很难进行自定义View 了,所以要先学习View的坐标系,网上也有很多介绍这些内容的文章比如
视图坐标系,
该文章中这张图一目了然的说明了哪些方法是相对于父布局的,哪些是相对于屏幕的,一定要搞清楚啊!!!
3. 自定义View的流程
不管是从网上还是书本上学习自定义View,有一点一定会让人印象深刻。一直在说自定义View一定要实现XXX方法。没错,onDraw方法是必须要实现的,不然界面就是一片空白,主要的绘制逻辑就是在onDraw()里完成的,而onMeasure,可以不实现,大不了使用Match_parent属性值或者明确指定View 的大小。onMeasure更重要的是用来设置当我们的属性值使用wrap_content时,View该怎么显示?
所以自定义View第一步还是要重写onMeasure,onDraw方法的。
下面自定义的一个简单的进度条:
上边的那个是我们自定义的效果,下边的那个是系统自带的Seekbar
思路:
-只需默认给出进度条的高度,View的高度在onMeasure()方法中根据进度条的高度进行计算
-需要声明4只画笔,一个画进度条背景,一个画当前进度,一个画滑块,还有一个写文字。
-在onMeasure()中设置View在不同模式下的大小。
-在onSizeChanged中根据View的宽高,计算滑块的宽度为高度的4/3,要明白滑块的高度和View的高度是一致的,所以这里直接使用了mBlockWidth=h*4/3
-在onDraw()中进行分别进行绘制。
package com.hank.ok.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Point;import android.support.annotation.Nullable;import android.text.TextPaint;import android.util.AttributeSet;import android.view.View;import android.view.WindowManager;/** * 类功能描述: * version:${version} */public class HorizontalProgress extends View { private int mScreenWidth;//屏幕宽度 private static final int mProgressHeight = 16;//进度条高度 private Paint mPaintBg; private Paint mPaintCurrent; private Paint mPaintBlock; private TextPaint mPaintText; private int mBlockWidth, mProgressWidth; private int mCurrentProgress = 0;//当前进度 public HorizontalProgress(Context context) { super(context); init(); } public HorizontalProgress(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init() { mScreenWidth = getScreenSize().x; mPaintBg = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaintBg.setStyle(Paint.Style.FILL); mPaintBg.setStrokeWidth(mProgressHeight); mPaintBg.setStrokeJoin(Paint.Join.ROUND); mPaintBg.setColor(Color.GRAY); mPaintCurrent = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaintCurrent.setStyle(Paint.Style.FILL); mPaintCurrent.setStrokeWidth(mProgressHeight); mPaintCurrent.setStrokeJoin(Paint.Join.ROUND); mPaintCurrent.setColor(Color.BLUE); mPaintText = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaintText.setTextSize(30); mPaintText.setColor(Color.WHITE); mPaintBlock = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaintBlock.setStyle(Paint.Style.FILL); mPaintBlock.setStrokeWidth(10); mPaintBlock.setStrokeJoin(Paint.Join.ROUND); mPaintBlock.setColor(Color.RED); setLayerType(LAYER_TYPE_HARDWARE, mPaintBlock); } /** * 计算屏幕宽高,存放在Point中 * * @return Point 含有屏幕宽高信息 */ private Point getScreenSize() { Point point = new Point(); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getSize(point); return point; } /** * 在此计算滑块的宽度以及进度条的最大宽度 * <p> * 该方法是会多次调用的 * * @param w View的宽度 * @param h View的高度 * @param oldw * @param oldh */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mBlockWidth = h * 4 / 3;//计算滑块的宽度,高度和View高度一致 mProgressWidth = w - mBlockWidth;//进度条的最大宽度=View的宽度-滑块的宽度 } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getMode(heightMeasureSpec); int heightResult = 0; if (heightMode == MeasureSpec.EXACTLY) { heightResult = heightSize; } else { //如果为没有明确指定View的高度并且使用的模式不是EXACTLY,为设置一个默认的高度 heightResult = mProgressHeight * 5; } int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthResult = 0; if (widthMode == MeasureSpec.EXACTLY) { widthResult = widthSize; } else { //如果为没有明确指定View的宽度并且使用的模式不是EXACTLY,就设置View的宽度为屏幕宽度 widthResult = mScreenWidth; } setMeasuredDimension(widthResult, heightResult); } /** * 入口 * * @param progress 当前进度值 */ public void setProgress(int progress) { mCurrentProgress = progress; invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawBackground(canvas); drawProgress(canvas); drawBlock(canvas); drawText(canvas); } /** * 因为文字一直都处于滑块的中间显示,所以文字的位置可以根据滑块的位置来确定 * * @param canvas */ private void drawText(Canvas canvas) { float centerX = mBlockWidth / 2 + mCurrentProgress * 1.0f / 100 * mProgressWidth;//滑块中心点x坐标 String text = mCurrentProgress + "%"; float textWidth = mPaintText.measureText(text); float startX = centerX - textWidth / 2; float baseline = getHeight() * 2 / 3; canvas.drawText(text, startX, baseline, mPaintText); } /** * 绘制滑块,只需要关注滑块的中心点坐标,即等于当前进度的坐标 * * @param canvas */ private void drawBlock(Canvas canvas) { float centerX = mBlockWidth / 2 + mCurrentProgress * 1.0f / 100 * mProgressWidth; float left = centerX - mBlockWidth / 2; float top = 0; float right = centerX + mBlockWidth / 2; float bottom = getBottom(); canvas.drawRect(left, top, right, bottom, mPaintBlock); } private void drawProgress(Canvas canvas) { float left = mBlockWidth / 2; float right = mCurrentProgress * 1.0f / 100 * mProgressWidth; float top = getHeight() / 2 - mProgressHeight / 2; float bottom = getHeight() / 2 + mProgressHeight / 2; canvas.drawRect(left, top, right, bottom, mPaintCurrent); } /** * 进度条背景的长度=View的宽度-滑块的宽度 * <p> * 起始X坐标:滑块宽度/2 * 终点X坐标=View的宽度-滑块宽度/2 * <p> * Y坐标一直垂直居中,也就是getHeigth()/2-进度条的高度/2 * * @param canvas */ private void drawBackground(Canvas canvas) { float left = mBlockWidth / 2; float right = getWidth() - mBlockWidth / 2; float top = getHeight() / 2 - mProgressHeight / 2; float bottom = getHeight() / 2 + mProgressHeight / 2; canvas.drawRect(left, top, right, bottom, mPaintBg); }}
主要的难点就在于对滑块坐标的计算上了,
float centerX = mBlockWidth / 2 + mCurrentProgress * 1.0f / 100 * mProgressWidth;
这个公式用来计算滑块的中心点坐标,left,right啥的都根据这个值+-滑块的宽度/2进行计算的。这个公式也不费解,以为滑块的默认起始位置是从View的最左边开始的,他的中心坐标的起始位置就是mBlockWidth/2,之后移动的时候,再加上移动的距离就可以了
- 自定义view总结一
- 自定义View知识总结
- Android 自定义View总结
- 自定义View总结
- Android 自定义View 总结
- 自定义View的总结
- 自定义View实战总结
- 自定义View总结
- 自定义View总结2
- 自定义View总结
- 自定义View步骤总结
- android自定义view总结
- 自定义View总结
- 自定义View方法总结
- 自定义View总结(一)
- 自定义View----总结
- 自定义View之总结
- 总结-自定义View
- webpack的基本介绍
- Android使用Fragment来实现TabHost的功能(解决切换Fragment状态不保存)以及各个Fragment之间的通信
- Java SE中代理模式(第一行代码)
- Spring Cloud构建微服务架构:分布式配置中心【Dalston版】
- python爬虫|爬取豆瓣电影TOP250并写入txt中
- 总结-自定义View
- 有道云笔记6.0去广告
- MySQL快速入门
- redis详解 -- 面试题
- 机器学习实战笔记(3.1)-朴素贝叶斯算法(原理分析)
- 【算法——Python实现】最小索引堆,最小堆的优化
- 02Architectural Overview 结构
- openwrt中使用ubus实现进程通信
- 【稀疏矩阵转置】线性时间复杂度实现稀疏矩阵转置