教你搞定Android自定义View

来源:互联网 发布:tempostorm 知乎 编辑:程序博客网 时间:2024/05/20 20:32

Android App开发过程中,很多时候会遇到系统框架中提供的控件无法满足我们产品的设计需求,那么这时候我们可以选择先Google下有没有比较成熟的开源项目可以让我们用,当然现在Github上面的项目非常丰富,能够满足我们绝不多数的开发需求,但是在使用这些炫酷的第三方控件时,我们也要想一想,我们是不是也可以发挥自己的想象力,动手实现自己想要的控件,尽可能掌控实现的细节!

View

Android所有的控件都是View或者View的子类,它其实表示的就是屏幕上的一块矩形区域,用一个Rect来表示,left,top表示View相对于它的parent View的起点,width,height表示View自己的宽高,通过这4个字段就能确定View在屏幕上的位置,确定位置后就可以开始绘制View的内容了。

View绘制过程

View的绘制可以分为下面三个过程:

  • Measure
    View会先做一次测量,算出自己需要占用多大的面积。View的Measure过程给我们暴露了一个接口onMeasure,方法的定义是这样的,

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}

    View类已经提供了一个基本的onMeasure实现,

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),          getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}public static int getDefaultSize(int size, int measureSpec) {  int result = size;  int specMode = MeasureSpec.getMode(measureSpec);  int specSize = MeasureSpec.getSize(measureSpec);  switch (specMode) {  case MeasureSpec.UNSPECIFIED:      result = size;      break;  case MeasureSpec.AT_MOST:  case MeasureSpec.EXACTLY:      result = specSize;      break;  }  return result;}

    其中invoke了setMeasuredDimension()方法,设置了measure过程中View的宽高,getSuggestedMinimumWidth()返回View的最小Width,Height也有对应的方法。插几句,MeasureSpec类是View类的一个内部静态类,它定义了三个常量UNSPECIFIED、AT_MOST、EXACTLY,其实我们可以这样理解它,它们分别对应LayoutParams中match_parent、wrap_content、xxxdp。我们可以重写onMeasure来重新定义View的宽高。

  • Layout
    Layout过程对于View类非常简单,同样View给我们暴露了onLayout方法

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

    因为我们现在讨论的是View,没有子View需要排列,所以这一步其实我们不需要做额外的工作。插一句,对ViewGroup类,onLayout方法中,我们需要将所有子View的大小宽高设置好,这个我们下一篇会详细说。

  • Draw
    Draw过程,就是在canvas上画出我们需要的View样式。同样View给我们暴露了onDraw方法

    protected void onDraw(Canvas canvas) {}

    默认View类的onDraw没有一行代码,但是提供给我们了一张空白的画布,举个例子,就像一张画卷一样,我们就是画家,能画出什么样的效果,完全取决我们。

    View中还有三个比较重要的方法

  • requestLayout
    View重新调用一次layout过程。

  • invalidate
    View重新调用一次draw过程

  • forceLayout
    标识View在下一次重绘,需要重新调用layout过程。

自定义属性

整个View的绘制流程我们已经介绍完了,还有一个很重要的知识,自定义控件属性,我们都知道View已经有一些基本的属性,比如layout_width,layout_height,background等,我们往往需要定义自己的属性,那么具体可以这么做。

  • 1.在values文件夹下,打开attrs.xml,其实这个文件名称可以是任意的,写在这里更规范一点,表示里面放的全是view的属性。
  • 2.因为我们下面的实例会用到2个长度,一个颜色值的属性,所以我们这里先创建3个属性。

    <declare-styleable name="rainbowbar">  <attr name="rainbowbar_hspace" format="dimension"></attr>  <attr name="rainbowbar_vspace" format="dimension"></attr>  <attr name="rainbowbar_color" format="color"></attr></declare-styleable>

那么到底怎么用呢,我们会看一个实例。

实现一个比较简单的Google彩虹进度条。

为了简单起见,这里我只用一种颜色,多种颜色就留给大家了,我们直接上代码。

蓝色的进度条
蓝色的进度条
public class RainbowBar extends View {  //progress bar color  int barColor = Color.parseColor("#1E88E5");  //every bar segment width  int hSpace = Utils.dpToPx(80, getResources());  //every bar segment height  int vSpace = Utils.dpToPx(4, getResources());  //space among bars  int space = Utils.dpToPx(10, getResources());  float startX = 0;  float delta = 10f;  Paint mPaint;  public RainbowBar(Context context) {    super(context);  }  public RainbowBar(Context context, AttributeSet attrs) {    this(context, attrs, 0);  }  public RainbowBar(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    //read custom attrs    TypedArray t = context.obtainStyledAttributes(attrs,            R.styleable.rainbowbar, 0, 0);    hSpace = t.getDimensionPixelSize(R.styleable.rainbowbar_rainbowbar_hspace, hSpace);    vSpace = t.getDimensionPixelOffset(R.styleable.rainbowbar_rainbowbar_vspace, vSpace);    barColor = t.getColor(R.styleable.rainbowbar_rainbowbar_color, barColor);    t.recycle();   // we should always recycle after used    mPaint = new Paint();    mPaint.setAntiAlias(true);    mPaint.setColor(barColor);    mPaint.setStrokeWidth(vSpace);  }  .......}

View有了三个构造方法需要我们重写,这里介绍下三个方法会被调用的场景,

  • 第一个方法,一般我们这样使用时会被调用,View view = new View(context);
  • 第二个方法,当我们在xml布局文件中使用View时,会在inflate布局时被调用,
    <View layout_width="match_parent" layout_height="match_parent"/>。
  • 第三个方法,跟第二种类似,但是增加style属性设置,这时inflater布局时会调用第三个构造方法。
    <View style="@styles/MyCustomStyle"layout_width="match_parent" layout_height="match_parent"/>。

上面大家可能会感觉到有点困惑的是,我把初始化读取自定义属性hspace,vspace,和barcolor的代码写在第三个构造方法里面,但是我RainbowBar在线性布局中没有加style属性(),那按照我们上面的解释,inflate布局时应该会invoke第二个构造方法啊,但是我们在第二个构造方法里面调用了第三个构造方法,this(context, attrs, 0); 所以在第三个构造方法中读取自定义属性,没有问题,这是一点小细节,避免代码冗余-,-

Draw

因为我们这里不用关注measrue和layout过程,直接重写onDraw方法即可。

 //draw be invoke numbers.int index = 0;@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    //get screen width    float sw = this.getMeasuredWidth();    if (startX >= sw + (hSpace + space) - (sw % (hSpace + space))) {        startX = 0;    } else {        startX += delta;    }    float start = startX;    // draw latter parse    while (start < sw) {        canvas.drawLine(start, 5, start + hSpace, 5, mPaint);        start += (hSpace + space);    }    start = startX - space - hSpace;    // draw front parse    while (start >= -hSpace) {        canvas.drawLine(start, 5, start + hSpace, 5, mPaint);        start -= (hSpace + space);    }    if (index >= 700000) {        index = 0;    }    invalidate();}//布局文件<?xml version="1.0" encoding="utf-8"?><LinearLayout     xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:layout_marginTop="40dp"android:orientation="vertical" ><com.sw.demo.widget.RainbowBar        android:layout_width="match_parent"    android:layout_height="wrap_content"    app:rainbowbar_color="@android:color/holo_blue_bright"    app:rainbowbar_hspace="80dp"    app:rainbowbar_vspace="10dp"    ></com.sw.demo.widget.RainbowBar></LinearLayout>

其实就是调用canvas的drawLine方法,然后每次将draw的起点向前推进,在方法的结尾,我们调用了invalidate方法,上面我们已经说明了,这个方法会让View重新调用onDraw方法,所以就达到我们的进度条一直在向前绘制的效果。下面是最后的显示效果,制作成gif时好像有色差,但是真实效果是蓝色的。我们只写了短短的几十行代码,自定义View并不是我们想象中那么难,下一篇我们会继续ViewGroup的绘制流程学习。

rainbow_bar_demo.gif
rainbow_bar_demo.gif


文/ALIOUS(简书作者)
原文链接:http://www.jianshu.com/p/84cee705b0d3
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 被期刊网骗了怎么办 发表的文章不想被收录怎么办? 农村长说的眼睛害了怎么办 普通党员不认同领导的决定怎么办 大学读不下去了怎么办 教师因病长期不能上班怎么办 长按win键黑屏了怎么办 巡视组巡视出问题后续怎么办 货运资格证两次没继续教育怎么办 电子注册备案表学信网查不到怎么办 学信网学籍档案没照片怎么办 信访局不给答复怎么办 发票跳了一个号怎么办 报税用的ca证书怎么办 报税u盘丢了怎么办 地税ca证书丢了怎么办 深圳ca证书丢了怎么办 武汉国税报税证书过期怎么办 江苏大学专业选修课挂了怎么办 电信翼企享福卡怎么办 教育部学籍在线验证报告过期怎么办 身份证被别人注册了学信网怎么办 大专文凭查不到学籍该怎么办 学信网上查不到学历怎么办 学信网上没有学历照片怎么办 学信网上没照片怎么办 学历认证报告丢了怎么办 学历认证弄丢了怎么办 手机系统安全证书有问题怎么办 台式电脑的浏览器证书出错怎么办 网上银行k宝密码忘了怎么办 工行证书介质已被锁定怎么办 学历认证是假的怎么办 怕被公司查学历怎么办 淘宝玩具没有怎么办3c 家庭遭遇小三我该怎么办 老公出轨把小三带回家了怎么办 小三怀孕了怎么办准生证 小三怀孕了起诉怎么办 不知情做了小三怎么办 发现自己被三了怎么办