自己定义View一直是横在Android开发人员面前的一道坎。

一、View和ViewGroup的关系

从View和ViewGroup的关系来看。ViewGroup继承View。

View的子类。多是功能型的控件。提供绘制的样式,比方imageView,TextView等。而ViewGroup的子类,多用于管理控件的大小,位置。如LinearLayout,RelativeLayout等。从下图能够看出

从实际应用中看,他们又是组合关系,我们在布局中,经常是一个ViewGroup嵌套多个ViewGroup或View。而被嵌套的ViewGroup又会嵌套多个ViewGroup或View

例如以下

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

二、View的绘制流程

从View源代码来看,主要关系三个方法:

1、measure():測量
     一个final方法,控制控件的大小
2、layout():布局
         用来控制自己的布局位置
          有相对性,仅仅相对于自己的父类布局,不关心祖宗布局
3、draw():绘制
          用来控制控件的显示样式

流程:  流程 measure --> layout --> draw

相应于我们要实现的方法是

onMeasure()
onLayout()
onDraw()

实际绘制中。我们的思考顺序通常是这种:

是否须要控制控件的大小-->是-->onMeasure()
(1)假设这个自己定义view不是ViewGroup,onMeasure()方法调用setMeasureDeminsion(width,height):用来设置自己的大小
(2)假设是ViewGroup,onMeasure()方法调用 。child.measure()測量孩子的大小。给出孩子的期望大小值,之后-->setMeasureDeminsion(width,height):用来设置自己的大小
是否须要控制控件的摆放位置-->是
-->onLayout
()
是否须要控制控件的样子-->是
-->onDraw ()-->canvas的绘制

以下是我绘制的流程图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

以下以自己定义滑动按钮为例,说明自己定义View的绘制流程

我们期待实现这种效果:

拖动或点击按钮,开关向右滑动,变成

当中开关能随着手指的触摸滑动到相应位置。直到最后才固定在开关位置上

新建一个类继承自View,实现其两个构造方法

  1. public class SwitchButtonView extends View {
  2.  
  3. public SwitchButtonView(Context context) {
  4. this(context, null);
  5. }
  6.  
  7. public SwitchButtonView(Context context, AttributeSet attrs) {
  8. super(context, attrs);
  9. }

drawable资源中加入这两张图片

借此,我们能够用onMeasure()确定这个控件的大小

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3.  
  4. mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
  5. mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);
  6. setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight());
  7. }

这个控件并不须要控制其摆放位置,略过onLayout();

接下来onDraw()确定其形状。

但我们须要依据我们点击控件的不同行为来确定形状,这须要重写onTouchEvent()

当中的逻辑是:

当按下时。触发事件MotionEvent.Action_Down。若此时状态为关:

(1)若手指触摸点(通过event.getX()得到)在按钮的 中线右側,按钮向右滑动一段距离(event.getX()与开关控件一半宽度之差,详细看下文源代码)

(2)若手指触摸点在按钮中线左側,按钮依然处于最左(即“关”的状态)。

若此时状态为开:

(1)若手指触摸点在按钮中线左側,按钮向左滑动一段距离

(2)若手指触摸点在按钮中线右側,按钮依然处于最右(即“开”的状态)

当滑动时。触发时间MotionEvent.Action_MOVE,逻辑与按下时一致

注意,onTouchEvent()须要设置返回值 为 Return true,否则无法响应滑动事件

当手指收起时。若开关中线位于整个控件中线左側,设置状态为关。反之,设置为开。

详细源代码例如以下所看到的:(还对外提供了一个暴露此时开关状态的接口)

自己定义View部分

  1. package com.lian.switchtogglebutton;
  2.  
  3. import android.content.Context;
  4. import android.graphics.Bitmap;
  5. import android.graphics.BitmapFactory;
  6. import android.graphics.Canvas;
  7. import android.graphics.Paint;
  8. import android.util.AttributeSet;
  9. import android.util.Log;
  10. import android.view.MotionEvent;
  11. import android.view.View;
  12.  
  13. /**
  14. * Created by lian on 2016/3/20.
  15. */
  16. public class SwitchButtonView extends View {
  17.  
  18. private static final int STATE_NULL = 0;//默认状态
  19. private static final int STATE_DOWN = 1;
  20. private static final int STATE_MOVE = 2;
  21. private static final int STATE_UP = 3;
  22.  
  23. private Bitmap mSlideButton;
  24. private Bitmap mSwitchButton;
  25. private Paint mPaint = new Paint();
  26. private int buttonState = STATE_NULL;
  27. private float mDistance;
  28. private boolean isOpened = false;
  29. private onSwitchListener mListener;
  30.  
  31. public SwitchButtonView(Context context) {
  32. this(context, null);
  33. }
  34.  
  35. public SwitchButtonView(Context context, AttributeSet attrs) {
  36. super(context, attrs);
  37. }
  38.  
  39. @Override
  40. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  41.  
  42. mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
  43. mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);
  44. setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight());
  45. }
  46.  
  47. @Override
  48. protected void onDraw(Canvas canvas) {
  49. super.onDraw(canvas);
  50. if (mSwitchButton!= null){
  51. canvas.drawBitmap(mSwitchButton, 0, 0, mPaint);
  52. }
  53. //buttonState的值在onTouchEvent()中确定
  54. switch (buttonState){
  55. case STATE_DOWN:
  56. case STATE_MOVE:
  57. if (!isOpened){
  58. float middle = mSlideButton.getWidth() / 2f;
  59. if (mDistance > middle) {
  60. float max = mSwitchButton.getWidth() - mSlideButton.getWidth();
  61. float left = mDistance - middle;
  62. if (left >= max) {
  63. left = max;
  64. }
  65. canvas.drawBitmap(mSlideButton,left,0,mPaint);
  66. }
  67.  
  68. else {
  69.  
  70. canvas.drawBitmap(mSlideButton,0,0,mPaint);
  71. }
  72. }else{
  73. float middle = mSwitchButton.getWidth() - mSlideButton.getWidth() / 2f;
  74. if (mDistance < middle){
  75. float left = mDistance-mSlideButton.getWidth()/2f;
  76. float min = 0;
  77. if (left < 0){
  78. left = min;
  79. }
  80. canvas.drawBitmap(mSlideButton,left,0,mPaint);
  81. }else{
  82. canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint);
  83. }
  84. }
  85.  
  86. break;
  87.  
  88. case STATE_NULL:
  89. case STATE_UP:
  90. if (isOpened){
  91. Log.d("开关","开着的");
  92. canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint);
  93. }else{
  94. Log.d("开关","关着的");
  95. canvas.drawBitmap(mSlideButton,0,0,mPaint);
  96. }
  97. break;
  98.  
  99. default:
  100. break;
  101. }
  102.  
  103. }
  104.  
  105. @Override
  106. public boolean onTouchEvent(MotionEvent event) {
  107.  
  108. switch (event.getAction()){
  109. case MotionEvent.ACTION_DOWN:
  110. mDistance = event.getX();
  111. Log.d("DOWN","按下");
  112. buttonState = STATE_DOWN;
  113. invalidate();
  114. break;
  115.  
  116. case MotionEvent.ACTION_MOVE:
  117. buttonState = STATE_MOVE;
  118. mDistance = event.getX();
  119. Log.d("MOVE","移动");
  120. invalidate();
  121. break;
  122.  
  123. case MotionEvent.ACTION_UP:
  124. mDistance = event.getX();
  125. buttonState = STATE_UP;
  126. Log.d("UP","起开");
  127. if (mDistance >= mSwitchButton.getWidth() / 2f){
  128. isOpened = true;
  129. }else {
  130. isOpened = false;
  131. }
  132. if (mListener != null){
  133. mListener.onSwitchChanged(isOpened);
  134. }
  135. invalidate();
  136. break;
  137. default:
  138. break;
  139. }
  140.  
  141. return true;
  142. }
  143.  
  144. public void setOnSwitchListener(onSwitchListener listener){
  145. this.mListener = listener;
  146. }
  147.  
  148. public interface onSwitchListener{
  149. void onSwitchChanged(boolean isOpened);
  150. }
  151. }

DemoActivity:

  1. package com.lian.switchtogglebutton;
  2.  
  3. import android.os.Bundle;
  4. import android.support.v7.app.AppCompatActivity;
  5. import android.widget.Toast;
  6.  
  7. public class MainActivity extends AppCompatActivity {
  8.  
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.activity_main);
  13.  
  14. SwitchButtonView switchButtonView = (SwitchButtonView) findViewById(R.id.switchbutton);
  15. switchButtonView.setOnSwitchListener(new SwitchButtonView.onSwitchListener() {
  16. @Override
  17. public void onSwitchChanged(boolean isOpened) {
  18. if (isOpened) {
  19. Toast.makeText(MainActivity.this, "打开", Toast.LENGTH_SHORT).show();
  20. }else {
  21. Toast.makeText(MainActivity.this, "关闭", Toast.LENGTH_SHORT).show();
  22. }
  23. }
  24. });
  25. }
  26. }

布局:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:paddingBottom="@dimen/activity_vertical_margin"
  8. android:paddingLeft="@dimen/activity_horizontal_margin"
  9. android:paddingRight="@dimen/activity_horizontal_margin"
  10. android:paddingTop="@dimen/activity_vertical_margin"
  11. tools:context="com.lian.switchtogglebutton.MainActivity">
  12.  
  13. <com.lian.switchtogglebutton.SwitchButtonView
  14. android:id="@+id/switchbutton"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. />
  18. </RelativeLayout>

图文剖析自己定义View的绘制(以自己定义滑动button为例)的更多相关文章

  1. 【Android自己定义View实战】之自己定义超简单SearchView搜索框

    [Android自己定义View实战]之自己定义超简单SearchView搜索框 这篇文章是对之前文章的翻新,至于为什么我要又一次改动这篇文章?原因例如以下 1.有人举报我抄袭,原文链接:http:/ ...

  2. 自己定义View之绘制圆环

    一.RingView 自己定义的view,构造器必须重写,至于重写哪个方法,參考例如以下: ①假设须要改变View绘制的图像,那么须要重写OnDraw方法.(这也是最经常使用的重写方式.) ②假设须要 ...

  3. 安卓自己定义View进阶-Canvas之绘制基本形状

    Canvas之绘制基本形状 作者微博: @GcsSloop [本系列相关文章] 在上一篇自己定义View分类与流程中我们了解自己定义View相关的基本知识,只是,这些东西依然还是理论,并不能拿来(zh ...

  4. 自己定义 View 基础和原理

    课程背景: 在 Android 提供的系统控件不能满足需求的情况下,往往须要自己开发自己定义 View 来满足需求,可是该怎样下手呢.本课程将带你进入自己定义 View 的开发过程,来了解它的一些原理 ...

  5. Android画图系列(二)——自己定义View绘制基本图形

    这个系列主要是介绍下Android自己定义View和Android画图机制.自己能力有限.假设在介绍过程中有什么错误.欢迎指正 前言 在上一篇Android画图系列(一)--自己定义View基础中我们 ...

  6. Android View的绘制过程

    首先是view的绘制过程~最主要的分三部分 measure layout draw 看字面意思,计算,布局,画~ android中控件相当于是画在一个无限大的画布上的,那就产生了几个问题 画布无限大, ...

  7. Android - View的绘制流程一(measure)

    该博文所用的demo结构图: 相应的代码: MainActivity.java: [java] view plain copy <span style="font-family:Mic ...

  8. 自定义控件(View的绘制流程源码解析)

    参考声明:这里的一些流程图援引自http://a.codekk.com/detail/Android/lightSky/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7% ...

  9. 【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布 ...

随机推荐

  1. Python join方法

    用相应格式的字符串将序列链接起来. a = ['wo','hao','shuai'] print("".join(a)) 'wohaoshuai' print("-&qu ...

  2. ATM+购物车商城

    模拟实现一个ATM + 购物商城程序 额度 15000或自定义 实现购物商城,买东西加入 购物车,调用信用卡接口结账 可以提现,手续费5% 支持多账户登录 支持账户间转账 记录每月日常消费流水 提供还 ...

  3. Linux 之 AT&T汇编语言 mov、add、sub指令、数据段

    mov指令的几种形式: mov 寄存器. 数据 mov ax,8888 mov 寄存器. 寄存器 mov bx,ax mov 寄存器. 内存单元 mov ax,[0] mov 内存单元.寄存器 mov ...

  4. django csrf_protect及浏览器同源策略

    1.django在检测post行为时会有诸多的限制. 为了防止跨域请求伪造安全 参考:http://www.qttc.net/201209211.html   https://www.cnblogs. ...

  5. 第一章 Python入门

    一. 语言 计算机语言:人和计算机之间沟通的语言计算机语言: 按照级别分类:(越高级月进阶人类) 机器语言: 汇编语言: 助记符 ag. add 2 3 高级语言: c, PHP, java , .n ...

  6. Python常用模块--json

    官方解释: JSON(JavaScript Object Notation)是一种轻量级的数据交换格式.人类很容易读写.机器很容易解析和生成.它基于 JavaScript编程语言的一部分, 标准ECM ...

  7. 零拷贝-zero copy

    Efficient data transfer through zero copy Zero Copy I: User-Mode Perspective 0. 前言 在阅读RocketMQ的官方文档时 ...

  8. SpringMvc 文件下载 详解

    最近SSM 需要用到文件下载,以前没用过,在百度上找了好久发现没有一篇博客,对于此段代码进行详细讲解, 这里是本人的个人总结,跟大家分享一下!!!不谢 /** * 文件下载 * ResponseEnt ...

  9. Django 面向对象orm

    django支持三种风格的模型继承: 1. 抽象类继承: 父类继承自models.Model, 但不会在数据库中生成相应的数据表.父类的属性列存储在其子类的数据表中 2. 多表继承: 多表继承的每个类 ...

  10. Java并发程序设计(十三)锁的性能优化

    锁的性能优化 一.优化注意事件 一)减少锁的持有时间 只在必要时进行同步,能明显减少锁的持有时间. 二)锁的细化 缺陷:当系统需要全局锁时,其消耗的资源会比较多. 三)锁的分离 比如读写分离锁 四)锁 ...