Android自定义控件
来源:互联网 发布:软件接口测试方法 编辑:程序博客网 时间:2024/06/03 07:31
1)Android控件
1)控件树
在Android中每个控件都占有一块矩形区域,控件一般分两类,View和ViewGroup,ViewGroup作为父控件可以包含多个子控件,并管理其包含的View控件。通过ViewGroup整个界面上的控件形成了一个树形结构,也就是常说的控件树,上层控件负责下层控件的测量和绘制,并传递交互事件,通常在Activity中使用findViewById()方法,就是在控件树中以树的深度优先遍历来查找对应元素。每个树的顶部都有一个DecorView对象,所有的交互事件都由它来统一调度和分配,从而可以对整个视图进行整体控制。下图展示了其树形结构.
2)UI界面架构图
Activity:基本的页面单元,Activity包含一个Window,window上可以绘制各种view
Window:表示顶层窗口,管理界面的显示和事件的响应;每个Activity均会创建一个
PhoneWindow对象,是Activity和整个View系统交互的接口,是Window的具体实现。PhoneWindow类内部包含了一个DecorView对象。简而言之,PhoneWindow是把一个FrameLayout进行了一定的包装,并提供了一组通用的窗口操作接口。
DecorView:是Window中View的RootView,设置窗口属性;该类是一个FrameLayout的子类,并且是PhoneWindow中的一个内部类。Decor的英文是Decoration,即“修饰”的意思,DecorView就是对普通的FrameLayout进行了一定的修饰,比如添加一个通用的Titlebar,并响应特定的按键消息等。
ViewRoot:它并不是一个View类型,而是一个Handler。
它的主要作用如下:
A. 向DecorView分发收到的用户发起的event事件,如按键,触屏,轨迹球等事件;
B. 与WindowManagerService交互,完成整个Activity的GUI的绘制。
3)绘制流程
整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为
根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘 (draw)。
2)View的测量
在Android中控件不会无缘无故出现在屏幕上,其内部必然经过大量的测量,计算布局和绘制,才会显示在界面上,而作为自定义控件中最重要的步骤测量也是最重要一环。
先来了解下几个重要概念
MeasureSpec 它是来帮助我们测量View的一个类,MeasureSpec是一个32位的int值,其中高2位为测量模式,低30位为测量的大小,通过这个可以获取测量模式和大小。
三种测量模式
EXACTLY
精确测量模式,当我们在布局中将宽高设置为具体数值或match_parent(占据父View的大小)时,系统使用这种测量模式。
AT_MOST
最大值模式,当控件的宽高设置为wrap_content时,此时控件大小随着子控件或内容的变化而变化,此时控件大小只要不超过父控件运行的大小就可以了。
UNSPECIFIED
不指定其大小测量模式,View想多大就多大,通常在绘制自定义View时使用,一般不用,可以不用管。
要测量,需要重写onMeasure()
看下一般测量流程的模板代码
可以看到先通过 MeasureSpec对象中取出测量的模式和大小,通过测量的模式,给出不同的测量值。当SpecMode为EXACTLY,就使用用户给定的测量大小,其他两种,需要给定一个默认的测量大小,当为AT_MOST时,需要取出系统测量值与默认值的最小值,这就是其大致测量模式。
3)View的绘制
当测量好了,我们就可以绘制了,我们需要重写onDraw()方法,
具体的绘制流程还是需要看不同的需求的,这后面再说。
4)ViewGroup的测量
ViewGroup管理其子View,其中负责子View的显示大小。
测量:ViewGroup的大小为wrap_content(即AT_MOST)时,ViewGroup对子View遍历,获取所有子View的大小(调用子View的Measure方法获取测量结果),从而决定自己的大小。其他模式下会通过具体的指定值来设置大小。
Layout过程:测量后需要将子View放到合适的位置。遍历子View的Layout方法,并制定其具体显示的位置,从而来具体决定其布局位置(可以重写onLayout()来控制子View的显示位置的逻辑,如果需要支持wrap_content属性,需重写onMeasure())。
5)ViewGroup的绘制
ViewGroup通常情况是不需要绘制的。 当指定背景颜色时,onDraw方法会被调用。 ViewGroup可以通过dispatchDraw方法来(遍历调用子View的onDraw方法)绘制子View。
6)自定义View
自定义View中通常用到的回调方法
onFinishInflate(): xml加载完毕之后回调
onSizeChanged(): 组件大小改变之后回调。
onMeasure(): 组件测量
onLayout(): 确定ViewGroup中子view的位置
onTouchEvent(): 监听触摸事件回调
onDraw(): 最常用,绘制View
一般有三种方法来实现自定义的控件:
1) 对现有组件进行拓展
2) 通过组合来实现新的控件
3) 重写View来实现全新的控件
7)重写View来实现全新的控件
前面两种就不多说了,蛮容易懂得,看下《Android群英传》就可以懂了
结合案例来说下使用
1)弧线展示图
package com.chen.demo.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.view.View;import android.view.WindowManager;/** * Created by chenxiaokang on 2016/12/1. */public class MyView extends View { private int length; private int mCircleXY; private float mRadius; private RectF mArcRectF; private int mSweepAngle; private Paint mPaint; private Paint mArcPaint; private Paint mTextPaint; private String mText; public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mSweepAngle = 187; //绘制圆形的画笔 mPaint = new Paint(); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); mPaint.setColor(Color.GREEN); //绘制弧形的画笔 mArcPaint = new Paint(); mArcPaint.setAntiAlias(true); mArcPaint.setStyle(Paint.Style.STROKE); mArcPaint.setStrokeWidth(24); mArcPaint.setColor(Color.RED); //绘制文字的画笔 mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); mTextPaint.setColor(Color.BLUE); mTextPaint.setTextSize(100); mTextPaint.setTextAlign(Paint.Align.CENTER); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); wm.getDefaultDisplay().getRealMetrics(metrics); length = Math.min(metrics.widthPixels, metrics.heightPixels) - 20; //中间的圆 mCircleXY = length/2; mRadius = length*0.5f/2; //弧线 mArcRectF = new RectF(length*0.1f, length*0.1f, length*0.9f, length*0.9f); mText = "全自定义View"; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(length, length); } @Override protected void onDraw(Canvas canvas) { //绘制内部圆 canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mPaint); //绘制弧线 canvas.drawArc(mArcRectF, 270, mSweepAngle, false, mArcPaint); //绘制文字 int textHeight = (int) (mTextPaint.descent()-mTextPaint.ascent()); canvas.drawText(mText, 0, mText.length(), mCircleXY, mCircleXY+textHeight/4, mTextPaint); }}
效果:
我们通过自定义View绘制了三个部分,内部圆,弧线,和文字,绘制了一个全新的控件。
2)音频图
package com.chen.demo.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.LinearGradient;import android.graphics.Paint;import android.graphics.Shader;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.view.View;import android.view.WindowManager;/** * Created by chenxiaokang on 2016/12/1. */public class MyViewB extends View{ private int mRectCount = 30; private int width; private int height; private int currentHeight; private int offset = 3; private int mRectWidth = 20; private int mStartPoint; private Paint paint; public MyViewB(Context context) { this(context, null); } public MyViewB(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyViewB(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); wm.getDefaultDisplay().getRealMetrics(metrics); width = metrics.widthPixels; height = (int) (metrics.heightPixels*1.0f*4/5); currentHeight = (int) (height*1.0f*3/5); mStartPoint = (width - mRectCount*mRectWidth)/2; paint = new Paint(); paint.setStyle(Paint.Style.FILL); paint.setAntiAlias(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(width, height); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); LinearGradient linearGradient = new LinearGradient(0, 0, width, height, Color.RED, Color.GREEN, Shader.TileMode.CLAMP); paint.setShader(linearGradient); } @Override protected void onDraw(Canvas canvas) { for(int i = 0; i<mRectCount; i++){ currentHeight = (int) (Math.random()*height); canvas.drawRect(mStartPoint+offset+mRectWidth*i, height-currentHeight, mStartPoint+mRectWidth*(i+1), height, paint); } postInvalidateDelayed(200); }}
效果:
就是那种音频跳动的效果
8)自定义ViewGroup
package com.chen.demo.view;import android.content.Context;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.WindowManager;import android.widget.Scroller;/** * Created by chenxiaokang on 2016/12/2. */public class MyViewGroup extends ViewGroup{ int width, height; Scroller mScroller; public MyViewGroup(Context context) { this(context, null); } public MyViewGroup(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); wm.getDefaultDisplay().getRealMetrics(metrics); width = metrics.widthPixels; height = metrics.heightPixels; mScroller = new Scroller(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); for (int i = 0; i<count; i++){ View view = getChildAt(i); measureChild(view, width, height); } } //放置每个子view的位置 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); MarginLayoutParams mlp = (MarginLayoutParams)getLayoutParams(); mlp.height = height * childCount; setLayoutParams(mlp); for(int i = 0; i<childCount; i++){ View view = getChildAt(i); if(view.getVisibility() != View.GONE){ view.layout(l, i*height, r, (i+1)*height); } } } int mLastY; int mStart; int mEnd; @Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mLastY = y; mStart = getScrollY(); break; case MotionEvent.ACTION_MOVE: if(!mScroller.isFinished()){ mScroller.abortAnimation(); } int dy = mLastY - y; if(getScrollY()<0){ dy = 0; } if(getScrollY() > getHeight()-height){ dy = 0; } scrollBy(0, dy); mLastY = y; break; case MotionEvent.ACTION_UP: mEnd = getScrollY(); int dScrollY = mEnd - mStart; if(dScrollY > 0){ if(dScrollY < height/3){ mScroller.startScroll(0, getScrollY(), 0, -dScrollY); }else { mScroller.startScroll(0, getScrollY(), 0, height - dScrollY); } }else { if(-dScrollY < height/3){ mScroller.startScroll(0, getScrollY(), 0, -dScrollY); }else { mScroller.startScroll(0, getScrollY(), 0, -height-dScrollY); } } break; } postInvalidate(); return true; } @Override public void computeScroll() { super.computeScroll(); if(mScroller.computeScrollOffset()){ scrollTo(0, mScroller.getCurrY()); postInvalidate(); } }}
效果:
9)总结
我们可以看见,自定义View并不是很难,只要安装基本流程一步步来,终将可以写出nb的控件,再厉害的控件都是由最基本的组件组成的,只要熟练掌握好这些控件的使用方法,难得也就不再难了。
10)Ref
1)《Android群英传》
2) http://www.cnblogs.com/cowboybusy/archive/2012/08/26/2718888.html
3)http://blog.csdn.net/guolin_blog/article/details/17357967
- [Android自定义控件] Android自定义控件
- Android自定义控件] Android自定义控件
- [Android自定义控件] Android自定义控件
- [Android自定义控件] Android自定义控件
- [Android自定义控件] Android自定义控件
- [Android自定义控件] Android自定义控件
- [Android自定义控件] Android自定义控件
- Android 自定义控件 单页翻书控件
- android虚线控件---自定义控件
- android自定义控件实例 --控件
- Android自定义控件--组合控件
- android 控件 自定义组合控件
- 【android自定义控件】ProgressBar自定义
- Android自定义控件 自定义属性
- Android自定义控件 -- 自定义View
- Android自定义控件 -- 自定义ViewGroup
- Android 自定义控件 自定义标题栏
- Android 自定义控件-自定义进度条。
- 系统自带分享的一些问题以及自定义功能
- 第十三周项目4-Floyd算法验证
- AndroidStudio 修改 Master Password
- 微信表情的字符编号完整版【图文并茂哦!】
- cocos2d-x 通过JNI实现c/c++和Android的java层函数互调
- Android自定义控件
- 第十三周项目-验证算法(4)
- Oracle中视图的创建和处理方法
- uft8编码范围
- 第十二周 项目4:利用遍历思想求解图问题(1)(2)
- java多线程之生产者消费者经典问题
- 负数在计算机中的转换过程
- ==和equal的区别
- PHPStudy Apache 配置支持HTTPS