欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-02-28 11:01:52
自定义view一直是横在android开发者面前的一道坎。 一、view和viewgroup的关系 从view和viewgroup的关系来看,viewgroup继承vi...

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

一、view和viewgroup的关系

从view和viewgroup的关系来看,viewgroup继承view。

view的子类,多是功能型的控件,提供绘制的样式,比如imageview,textview等,而viewgroup的子类,多用于管理控件的大小,位置,如linearlayout,relativelayout等,从下图可以看出

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

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

如下

自定义滑动按钮为例图文剖析Android自定义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的绘制

下面是我绘制的流程图:

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

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

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

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

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

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

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

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

public class switchbuttonview extends view { 
 
   
  public switchbuttonview(context context) { 
    this(context, null); 
  } 
 
  public switchbuttonview(context context, attributeset attrs) { 
    super(context, attrs); 
  } 

drawable资源中添加这两张图片

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

借此,我们可以用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 < 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> 

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