Android自定义View的使用

来源:互联网 发布:唐筛报告怎么看 知乎 编辑:程序博客网 时间:2024/06/03 13:32

原文地址:http://blog.csdn.net/chen_lian_/article/details/50939902

自定义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,实现其两个构造方法

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class SwitchButtonView extends View {  
  2.   
  3.       
  4.     public SwitchButtonView(Context context) {  
  5.         this(context, null);  
  6.     }  
  7.   
  8.     public SwitchButtonView(Context context, AttributeSet attrs) {  
  9.         super(context, attrs);  
  10.     }  

drawable资源中添加这两张图片


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

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.   
  4.         mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);  
  5.         mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);  
  6.         setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight());  
  7.     }  

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


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

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

其中的逻辑是:

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

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

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

若此时状态为开:

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

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


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

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


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

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


自定义View部分


[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.lian.switchtogglebutton;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Bitmap;  
  5. import android.graphics.BitmapFactory;  
  6. import android.graphics.Canvas;  
  7. import android.graphics.Paint;  
  8. import android.util.AttributeSet;  
  9. import android.util.Log;  
  10. import android.view.MotionEvent;  
  11. import android.view.View;  
  12.   
  13. /** 
  14.  * Created by lian on 2016/3/20. 
  15.  */  
  16. public class SwitchButtonView extends View {  
  17.   
  18.     private static final int STATE_NULL = 0;//默认状态  
  19.     private static final int STATE_DOWN = 1;  
  20.     private static final int STATE_MOVE = 2;  
  21.     private static final int STATE_UP = 3;  
  22.   
  23.     private Bitmap mSlideButton;  
  24.     private Bitmap mSwitchButton;  
  25.     private Paint mPaint = new Paint();  
  26.     private int buttonState = STATE_NULL;  
  27.     private float mDistance;  
  28.     private boolean isOpened = false;  
  29.     private onSwitchListener mListener;  
  30.   
  31.     public SwitchButtonView(Context context) {  
  32.         this(context, null);  
  33.     }  
  34.   
  35.     public SwitchButtonView(Context context, AttributeSet attrs) {  
  36.         super(context, attrs);  
  37.     }  
  38.   
  39.     @Override  
  40.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  41.   
  42.         mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);  
  43.         mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);  
  44.         setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight());  
  45.     }  
  46.   
  47.     @Override  
  48.     protected void onDraw(Canvas canvas) {  
  49.         super.onDraw(canvas);  
  50.         if (mSwitchButton!= null){  
  51.             canvas.drawBitmap(mSwitchButton, 00, mPaint);  
  52.         }  
  53.         //buttonState的值在onTouchEvent()中确定  
  54.         switch (buttonState){  
  55.             case STATE_DOWN:  
  56.             case STATE_MOVE:  
  57.                 if (!isOpened){  
  58.                     float middle = mSlideButton.getWidth() / 2f;  
  59.                     if (mDistance > middle) {  
  60.                         float max = mSwitchButton.getWidth() - mSlideButton.getWidth();  
  61.                         float left = mDistance - middle;  
  62.                         if (left >= max) {  
  63.                             left = max;  
  64.                         }  
  65.                         canvas.drawBitmap(mSlideButton,left,0,mPaint);  
  66.                     }  
  67.   
  68.                     else {  
  69.   
  70.                         canvas.drawBitmap(mSlideButton,0,0,mPaint);  
  71.                     }  
  72.                 }else{  
  73.                     float middle = mSwitchButton.getWidth() - mSlideButton.getWidth() / 2f;  
  74.                     if (mDistance < middle){  
  75.                         float left = mDistance-mSlideButton.getWidth()/2f;  
  76.                         float min = 0;  
  77.                         if (left < 0){  
  78.                             left = min;  
  79.                         }  
  80.                         canvas.drawBitmap(mSlideButton,left,0,mPaint);  
  81.                     }else{  
  82.                         canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint);  
  83.                     }  
  84.                 }  
  85.   
  86.   
  87.   
  88.                 break;  
  89.   
  90.             case STATE_NULL:  
  91.             case STATE_UP:  
  92.                 if (isOpened){  
  93.                     Log.d("开关","开着的");  
  94.                     canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint);  
  95.                 }else{  
  96.                     Log.d("开关","关着的");  
  97.                     canvas.drawBitmap(mSlideButton,0,0,mPaint);  
  98.                 }  
  99.                 break;  
  100.   
  101.             default:  
  102.                 break;  
  103.         }  
  104.   
  105.     }  
  106.   
  107.     @Override  
  108.     public boolean onTouchEvent(MotionEvent event) {  
  109.   
  110.         switch (event.getAction()){  
  111.             case MotionEvent.ACTION_DOWN:  
  112.                 mDistance = event.getX();  
  113.                 Log.d("DOWN","按下");  
  114.                 buttonState = STATE_DOWN;  
  115.                 invalidate();  
  116.                 break;  
  117.   
  118.             case MotionEvent.ACTION_MOVE:  
  119.                 buttonState = STATE_MOVE;  
  120.                 mDistance = event.getX();  
  121.                 Log.d("MOVE","移动");  
  122.                 invalidate();  
  123.                 break;  
  124.   
  125.             case MotionEvent.ACTION_UP:  
  126.                 mDistance = event.getX();  
  127.                 buttonState = STATE_UP;  
  128.                 Log.d("UP","起开");  
  129.                 if (mDistance >= mSwitchButton.getWidth() / 2f){  
  130.                     isOpened = true;  
  131.                 }else {  
  132.                     isOpened = false;  
  133.                 }  
  134.                 if (mListener != null){  
  135.                     mListener.onSwitchChanged(isOpened);  
  136.                 }  
  137.                 invalidate();  
  138.                 break;  
  139.             default:  
  140.                 break;  
  141.         }  
  142.   
  143.         return true;  
  144.     }  
  145.   
  146.     public void setOnSwitchListener(onSwitchListener listener){  
  147.         this.mListener = listener;  
  148.     }  
  149.   
  150.     public interface onSwitchListener{  
  151.         void onSwitchChanged(boolean isOpened);  
  152.     }  
  153. }  


DemoActivity:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.lian.switchtogglebutton;  
  2.   
  3. import android.os.Bundle;  
  4. import android.support.v7.app.AppCompatActivity;  
  5. import android.widget.Toast;  
  6.   
  7. public class MainActivity extends AppCompatActivity {  
  8.   
  9.     @Override  
  10.     protected void onCreate(Bundle savedInstanceState) {  
  11.         super.onCreate(savedInstanceState);  
  12.         setContentView(R.layout.activity_main);  
  13.   
  14.         SwitchButtonView switchButtonView = (SwitchButtonView) findViewById(R.id.switchbutton);  
  15.         switchButtonView.setOnSwitchListener(new SwitchButtonView.onSwitchListener() {  
  16.             @Override  
  17.             public void onSwitchChanged(boolean isOpened) {  
  18.                 if (isOpened) {  
  19.                     Toast.makeText(MainActivity.this"打开", Toast.LENGTH_SHORT).show();  
  20.                 }else {  
  21.                     Toast.makeText(MainActivity.this"关闭", Toast.LENGTH_SHORT).show();  
  22.                 }  
  23.             }  
  24.         });  
  25.     }  
  26. }  

布局:


[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     xmlns:tools="http://schemas.android.com/tools"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     android:paddingBottom="@dimen/activity_vertical_margin"  
  8.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  9.     android:paddingRight="@dimen/activity_horizontal_margin"  
  10.     android:paddingTop="@dimen/activity_vertical_margin"  
  11.     tools:context="com.lian.switchtogglebutton.MainActivity">  
  12.   
  13.     <com.lian.switchtogglebutton.SwitchButtonView  
  14.         android:id="@+id/switchbutton"  
  15.         android:layout_width="wrap_content"  
  16.         android:layout_height="wrap_content"  
  17.         />  
  18. </RelativeLayout>  
0 0
原创粉丝点击