热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

自定义滑动按钮为例图文剖析Android自定义View绘制

这篇文章主要介绍了自定义滑动按钮的例子,图文剖析Android自定义View绘制,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

一、View和ViewGroup的关系

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

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

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

如下

二、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的绘制

下面是我绘制的流程图:

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

我们期待实现这样的效果:

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

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

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

public class SwitchButtonView extends View { 
 
  
 public SwitchButtonView(Context context) { 
  this(context, null); 
 } 
 
 public SwitchButtonView(Context context, AttributeSet attrs) { 
  super(context, attrs); 
 } 

drawable资源中添加这两张图片

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

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

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

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

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

其中的逻辑是:

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

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

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

若此时状态为开:

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

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

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

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

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

具体源码如下所示:(还对外提供了一个暴露此时开关状态的接口)

自定义View部分

package com.lian.switchtogglebutton; 
 
import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.Canvas; 
import android.graphics.Paint; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.view.MotionEvent; 
import android.view.View; 
 
/** 
 * Created by lian on 2016/3/20. 
 */ 
public class SwitchButtonView extends View { 
 
 private static final int STATE_NULL = 0;//默认状态 
 private static final int STATE_DOWN = 1; 
 private static final int STATE_MOVE = 2; 
 private static final int STATE_UP = 3; 
 
 private Bitmap mSlideButton; 
 private Bitmap mSwitchButton; 
 private Paint mPaint = new Paint(); 
 private int buttOnState= STATE_NULL; 
 private float mDistance; 
 private boolean isOpened = false; 
 private onSwitchListener mListener; 
 
 public SwitchButtonView(Context context) { 
  this(context, null); 
 } 
 
 public SwitchButtonView(Context context, AttributeSet attrs) { 
  super(context, attrs); 
 } 
 
 @Override 
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
 
  mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); 
  mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background); 
  setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight()); 
 } 
 
 @Override 
 protected void onDraw(Canvas canvas) { 
  super.onDraw(canvas); 
  if (mSwitchButton!= null){ 
   canvas.drawBitmap(mSwitchButton, 0, 0, mPaint); 
  } 
  //buttonState的值在onTouchEvent()中确定 
  switch (buttonState){ 
   case STATE_DOWN: 
   case STATE_MOVE: 
    if (!isOpened){ 
     float middle = mSlideButton.getWidth() / 2f; 
     if (mDistance > middle) { 
      float max = mSwitchButton.getWidth() - mSlideButton.getWidth(); 
      float left = mDistance - middle; 
      if (left >= max) { 
       left = max; 
      } 
      canvas.drawBitmap(mSlideButton,left,0,mPaint); 
     } 
 
     else { 
 
      canvas.drawBitmap(mSlideButton,0,0,mPaint); 
     } 
    }else{ 
     float middle = mSwitchButton.getWidth() - mSlideButton.getWidth() / 2f; 
     if (mDistance = mSwitchButton.getWidth() / 2f){ 
     isOpened = true; 
    }else { 
     isOpened = false; 
    } 
    if (mListener != null){ 
     mListener.onSwitchChanged(isOpened); 
    } 
    invalidate(); 
    break; 
   default: 
    break; 
  } 
 
  return true; 
 } 
 
 public void setOnSwitchListener(onSwitchListener listener){ 
  this.mListener = listener; 
 } 
 
 public interface onSwitchListener{ 
  void onSwitchChanged(boolean isOpened); 
 } 
} 

DemoActivity:

package com.lian.switchtogglebutton; 
 
import android.os.Bundle; 
import android.support.v7.app.AppCompatActivity; 
import android.widget.Toast; 
 
public class MainActivity extends AppCompatActivity { 
 
 @Override 
 protected void onCreate(Bundle savedInstanceState) { 
  super.onCreate(savedInstanceState); 
  setContentView(R.layout.activity_main); 
 
  SwitchButtonView switchButtOnView= (SwitchButtonView) findViewById(R.id.switchbutton); 
  switchButtonView.setOnSwitchListener(new SwitchButtonView.onSwitchListener() { 
   @Override 
   public void onSwitchChanged(boolean isOpened) { 
    if (isOpened) { 
     Toast.makeText(MainActivity.this, "打开", Toast.LENGTH_SHORT).show(); 
    }else { 
     Toast.makeText(MainActivity.this, "关闭", Toast.LENGTH_SHORT).show(); 
    } 
   } 
  }); 
 } 
} 

布局:

<&#63;xml version="1.0" encoding="utf-8"&#63;> 
 
 
  
 

以上就是本文的全部内容,希望对大家的学习有所帮助。


推荐阅读
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 解决JAX-WS动态客户端工厂弃用问题并迁移到XFire
    在处理Java项目中的JAR包冲突时,我们遇到了JaxWsDynamicClientFactory被弃用的问题,并成功将其迁移到org.codehaus.xfire.client。本文详细介绍了这一过程及解决方案。 ... [详细]
  • 本文探讨了 RESTful API 和传统接口之间的关键差异,解释了为什么 RESTful API 在设计和实现上具有独特的优势。 ... [详细]
  • 本文详细介绍了如何使用Spring Boot进行高效开发,涵盖了配置、实例化容器以及核心注解的使用方法。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • Python自动化处理:从Word文档提取内容并生成带水印的PDF
    本文介绍如何利用Python实现从特定网站下载Word文档,去除水印并添加自定义水印,最终将文档转换为PDF格式。该方法适用于批量处理和自动化需求。 ... [详细]
  • 在当前众多持久层框架中,MyBatis(前身为iBatis)凭借其轻量级、易用性和对SQL的直接支持,成为许多开发者的首选。本文将详细探讨MyBatis的核心概念、设计理念及其优势。 ... [详细]
  • 将Web服务部署到Tomcat
    本文介绍了如何在JDeveloper 12c中创建一个Java项目,并将其打包为Web服务,然后部署到Tomcat服务器。内容涵盖从项目创建、编写Web服务代码、配置相关XML文件到最终的本地部署和验证。 ... [详细]
  • XNA 3.0 游戏编程:从 XML 文件加载数据
    本文介绍如何在 XNA 3.0 游戏项目中从 XML 文件加载数据。我们将探讨如何将 XML 数据序列化为二进制文件,并通过内容管道加载到游戏中。此外,还会涉及自定义类型读取器和写入器的实现。 ... [详细]
  • 本文介绍如何在 Unity 的 XML 配置文件中,将参数传递给自定义生命周期管理器的构造函数。我们将详细探讨 CustomLifetimeManager 类的实现及其配置方法。 ... [详细]
  • 本文详细介绍了 Java 中 org.apache.xmlbeans.SchemaType 类的 getBaseEnumType() 方法,提供了多个代码示例,并解释了其在不同场景下的使用方法。 ... [详细]
  • 本文详细介绍了如何解决MyBatis中常见的BindingException错误,提供了多种排查和修复方法,确保Mapper接口与XML文件的正确配置。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
author-avatar
手机用户2502932937
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有