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

作者:C_L 时间:2023-06-05 01:34:34 

自定义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>
标签:Android,View,滑动按钮
0
投稿

猜你喜欢

  • Android8.1原生系统网络感叹号消除的方法

    2022-09-29 18:38:52
  • C#中使用HttpPost调用WebService的方法

    2023-01-17 18:34:36
  • Android如何获取系统通知的开启状态详解

    2021-12-28 05:45:09
  • Java解析xml文件遇到特殊符号异常的情况(处理方案)

    2023-10-23 17:31:48
  • Android Studio中生成aar文件及本地方式使用aar文件的方法

    2022-06-14 23:43:59
  • Java实现TopK问题的方法

    2023-11-10 20:32:14
  • java-spark中各种常用算子的写法示例

    2023-04-28 23:21:01
  • 详解Java内部类与对象的打印概念和流程

    2021-10-10 21:36:56
  • Android 代码写控件代替XML简单实例

    2023-04-11 00:10:48
  • 基于Kubernetes实现前后端应用的金丝雀发布(两种方案)

    2023-01-07 02:32:27
  • Groovy的规则脚本引擎实例解读

    2023-07-11 21:24:04
  • Java基础之Web服务器与Http详解

    2021-08-13 16:39:42
  • Android实现仿360桌面悬浮清理内存

    2021-08-25 11:21:05
  • Spring五大类注解读取存储Bean对象的方法

    2023-11-09 22:07:07
  • java8新特性-lambda表达式入门学习心得

    2021-09-26 17:14:33
  • Java 队列实现原理及简单实现代码

    2021-07-19 05:12:56
  • Java Scanner类用法及nextLine()产生的换行符问题实例分析

    2022-12-22 21:44:04
  • Java编程调用微信分享功能示例

    2022-10-16 06:39:49
  • 详解Java中Period类的使用方法

    2023-11-28 21:04:44
  • 如何基于JavaFX开发桌面程序

    2023-10-30 11:06:19
  • asp之家 软件编程 m.aspxhome.com