Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義滑動按鈕為例圖文剖析Android自定義View繪制

自定義滑動按鈕為例圖文剖析Android自定義View繪制

編輯:關於Android編程

自定義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 < 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> 

以上就是本文的全部內容,希望對大家的學習有所幫助。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved