广告
返回顶部
首页 > 资讯 > 移动开发 >自定义滑动按钮为例图文剖析Android自定义View绘制
  • 412
分享到

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

自定义view按钮Android 2022-06-06 08:06:18 412人浏览 八月长安
摘要

自定义View一直是横在Android开发者面前的一道坎。 一、View和ViewGroup的关系 从View和ViewGroup的关系来看,ViewGroup继承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; 
 
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 < middle){ 
            float left = mDistance-mSlideButton.getWidth()/2f; 
            float min = 0; 
            if (left < 0){ 
              left = min; 
            } 
            canvas.drawBitmap(mSlideButton,left,0,mPaint); 
          }else{ 
            canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint); 
          } 
        } 
        break; 
      case STATE_NULL: 
      case STATE_UP: 
        if (isOpened){ 
          Log.d("开关","开着的"); 
          canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint); 
        }else{ 
          Log.d("开关","关着的"); 
          canvas.drawBitmap(mSlideButton,0,0,mPaint); 
        } 
        break; 
      default: 
        break; 
    } 
  } 
  @Override 
  public boolean onTouchEvent(MotionEvent event) { 
    switch (event.getAction()){ 
      case MotionEvent.ACTION_DOWN: 
        mDistance = event.getX(); 
        Log.d("DOWN","按下"); 
        buttonState = STATE_DOWN; 
        invalidate(); 
        break; 
      case MotionEvent.ACTION_MOVE: 
        buttonState = STATE_MOVE; 
        mDistance = event.getX(); 
        Log.d("MOVE","移动"); 
        invalidate(); 
        break; 
      case MotionEvent.ACTION_UP: 
        mDistance = event.getX(); 
        buttonState = STATE_UP; 
        Log.d("UP","起开"); 
        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(); 
        } 
      } 
    }); 
  } 
} 

布局:


<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout 
  xmlns:android="Http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:paddingBottom="@dimen/activity_vertical_margin" 
  android:paddingLeft="@dimen/activity_horizontal_margin" 
  android:paddingRight="@dimen/activity_horizontal_margin" 
  android:paddingTop="@dimen/activity_vertical_margin" 
  tools:context="com.lian.switchtogglebutton.MainActivity"> 
  <com.lian.switchtogglebutton.SwitchButtonView 
    android:id="@+id/switchbutton" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    /> 
</RelativeLayout> 
您可能感兴趣的文章:android为ListView每个Item上面的按钮添加事件android中在Activity中响应ListView内部按钮的点击事件的两种方法Android自定义View制作动态炫酷按钮实例解析Android自定义View实现拖动选择按钮Android ListView实现仿iPhone实现左滑删除按钮的简单实例Android ListView ImageView实现单选按钮实例Android自定义View之圆形进度条式按钮Android自定义View实现开关按钮Android基于ImageView绘制的开关按钮效果示例Android自定义View实现可展开、会呼吸的按钮


--结束END--

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

本文链接: https://www.lsjlt.com/news/25422.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作