zoukankan      html  css  js  c++  java
  • 自定义控件(视图)2期笔记05:自定义控件之继承自View(滑动开关)

    1.  开关按钮点击效果,如下:

    2. 继承已有View实现自定义View

    3. 下面通过一个案例实现滑动开关的案例:

    (1)新建一个新的Android工程,命名为" 开关按钮",接下来我们按照上面的步骤来:自定义类MyToggleButton继承自View

    (2)编写设计activity_main.xml布局文件,如下:

     1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     2     xmlns:tools="http://schemas.android.com/tools"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     tools:context="com.himi.togglebtn.MainActivity" >
     6 
     7     <com.himi.togglebtn.MyToggleButton
     8         android:layout_width="wrap_content"
     9         android:layout_height="wrap_content"
    10         android:id="@+id/my_toggle_btn" 
    11         android:layout_centerHorizontal="true"
    12         android:layout_centerVertical="true"/>
    13 
    14 </RelativeLayout>

    注意这里使用的自定的MyToggleButton(View),要使用全路径

    (3)编写自定义的MyToggleButton。

    • 重写onMeasure()方法,指定控件大小

    • 重写onDraw()方法,绘制控件内容

      1 package com.himi.togglebtn;
      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.view.View;
     10 import android.view.View.OnClickListener;
     11 
     12 public class MyToggleButton extends View implements OnClickListener {
     13 
     14     //作为背景的图片
     15     private Bitmap backgroundBitmap;
     16     //滑动的开关图片
     17     private Bitmap slidebtn;
     18     private Paint paint;
     19     
     20     //滑动按钮的左边界
     21     private float slidebtn_left;
     22     
     23     /**
     24      * 当前开关的状态
     25      * true :为开
     26      * false:为关
     27      */
     28     private boolean currState = false;
     29     
     30 
     31     /**
     32      * 我们在代码里面创建对象的时候,使用此构造方法
     33      * @param context
     34      */
     35     public MyToggleButton(Context context) {
     36         super(context);
     37         // TODO 自动生成的构造函数存根
     38     }
     39     
     40     /**
     41      * 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。
     42      * 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造)
     43      * @param context
     44      * @param attrs
     45      */
     46     public MyToggleButton(Context context, AttributeSet attrs) {
     47         super(context, attrs);
     48         initView();
     49     }
     50 
     51     
     52     /**
     53      * 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle
     54      * 改变生成自定义的View的样式style
     55      * @param context
     56      * @param attrs
     57      * @param defStyle
     58      */
     59     public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
     60         super(context, attrs, defStyle);
     61         // TODO 自动生成的构造函数存根
     62     }
     63 
     64     
     65     //初始化
     66     private void initView() {
     67         //初始化图片
     68         backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
     69         slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);
     70         //初始化画笔
     71         paint = new Paint();
     72         paint.setAntiAlias(true);//打开抗锯齿
     73         //添加Onclick事件监听
     74         setOnClickListener(this);
     75     }
     76     
     77     /*
     78      * View对象显示在屏幕上,有几个重要步骤:
     79      * 1. 构造方法 创建  对象.
     80      * 2. 测量View的大小.  onMeasure(int, int):系统调用的方法,获知View的大小
     81      * 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用
     82      * 4. 绘制View的内容           onDraw(canvas)
     83      * 
     84      */
     85     
     86     
     87     
     88     /**
     89      * 
     90      * 测量尺寸时候的回调方法
     91      */
     92     @Override
     93     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     94         
     95         //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     96         /**
     97          * 设置当前View的大小
     98          * width :当前View的宽度
     99          * height:当前view的高度(单位:像素)
    100          */
    101         setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
    102     }
    103     
    104     
    105     /**
    106      * 自定义的View,作用不大
    107      * 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法
    108      */
    109     @Override
    110     protected void onLayout(boolean changed, int left, int top, int right,
    111             int bottom) {
    112         // TODO 自动生成的方法存根
    113         super.onLayout(changed, left, top, right, bottom);
    114     }
    115     
    116     /**
    117      * 绘制当前View的内容
    118      */
    119     @Override
    120     protected void onDraw(Canvas canvas) {
    121         // TODO 自动生成的方法存根
    122         //super.onDraw(canvas);
    123         //绘制背景图
    124         /*
    125          * backgroundBitmap:要绘制的图片
    126          * left 图片的左边界
    127          * top 图片的上边界
    128          * paint 绘制图片要使用的画笔
    129          */
    130         canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
    131         //绘制可滑动的按钮
    132         canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint);
    133     }
    134 
    135     public void onClick(View v) {
    136         currState = ! currState;
    137         flushState();//刷新当前开关状态
    138         
    139     }
    140 
    141     /**
    142      * 刷新当前开关视图
    143      */
    144     private void flushState() {
    145         if(currState) {
    146             slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth();
    147         }else {
    148             slidebtn_left =0;
    149         }
    150         
    151         /*
    152          * invalidate():刷新当前View,会导致onDraw方法的执行
    153          * 上面只是设置参数设置,下面必须将上面的参数传递到onDraw方法中,利用onDraw方法重新绘制,才能实现刷新的效果
    154          * 
    155          */
    156         invalidate();
    157         
    158     }
    159     
    160 
    161 
    162 
    163 }

    代码逻辑如下:

    备注:

      初始状态slideBtn 左边为0

              

      开的时候slideBtn left值为background.width-slidebtn.width

              

    与此同时,MainActivity.java,如下:

     1 package com.himi.togglebtn;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 
     6 public class MainActivity extends Activity {
     7 
     8     @Override
     9     protected void onCreate(Bundle savedInstanceState) {
    10         super.onCreate(savedInstanceState);
    11         setContentView(R.layout.activity_main);
    12     }
    13 
    14 }

    (4)布署程序到模拟器上如下:

    (5)实现开关的弹性滑动,上面的只能让开关左右切换不能手机拖动滑动开关,用户体验不好,我们要优化。

    MainActivity.java,修改为:

      1 package com.himi.togglebtn;
      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.view.MotionEvent;
     10 import android.view.View;
     11 import android.view.View.OnClickListener;
     12 
     13 public class MyToggleButton extends View implements OnClickListener {
     14 
     15     //作为背景的图片
     16     private Bitmap backgroundBitmap;
     17     //滑动的开关图片
     18     private Bitmap slidebtn;
     19     private Paint paint;
     20     
     21     //滑动按钮的左边界
     22     private float slidebtn_left;
     23     
     24     /**
     25      * 当前开关的状态
     26      * true :为开
     27      * false:为关
     28      */
     29     private boolean currState = false;
     30     
     31 
     32     /**
     33      * 我们在代码里面创建对象的时候,使用此构造方法
     34      * @param context
     35      */
     36     public MyToggleButton(Context context) {
     37         super(context);
     38         // TODO 自动生成的构造函数存根
     39     }
     40     
     41     /**
     42      * 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。
     43      * 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造)
     44      * @param context
     45      * @param attrs
     46      */
     47     public MyToggleButton(Context context, AttributeSet attrs) {
     48         super(context, attrs);
     49         initView();
     50     }
     51 
     52     
     53     /**
     54      * 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle
     55      * 改变生成自定义的View的样式style
     56      * @param context
     57      * @param attrs
     58      * @param defStyle
     59      */
     60     public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
     61         super(context, attrs, defStyle);
     62         // TODO 自动生成的构造函数存根
     63     }
     64 
     65     
     66     //初始化
     67     private void initView() {
     68         //初始化图片
     69         backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
     70         slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);
     71         //初始化画笔
     72         paint = new Paint();
     73         paint.setAntiAlias(true);//打开抗锯齿
     74         //添加Onclick事件监听
     75         setOnClickListener(this);
     76     }
     77     
     78     /*
     79      * View对象显示在屏幕上,有几个重要步骤:
     80      * 1. 构造方法 创建  对象.
     81      * 2. 测量View的大小.  onMeasure(int, int):系统调用的方法,获知View的大小
     82      * 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用
     83      * 4. 绘制View的内容           onDraw(canvas)
     84      * 
     85      */
     86     
     87     
     88     
     89     /**
     90      * 
     91      * 测量尺寸时候的回调方法
     92      */
     93     @Override
     94     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     95         
     96         //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     97         /**
     98          * 设置当前View的大小
     99          * width :当前View的宽度
    100          * height:当前view的高度(单位:像素)
    101          */
    102         setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
    103     }
    104     
    105     
    106     /**
    107      * 自定义的View,作用不大
    108      * 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法
    109      */
    110     @Override
    111     protected void onLayout(boolean changed, int left, int top, int right,
    112             int bottom) {
    113         // TODO 自动生成的方法存根
    114         super.onLayout(changed, left, top, right, bottom);
    115     }
    116     
    117     /**
    118      * 绘制当前View的内容
    119      */
    120     @Override
    121     protected void onDraw(Canvas canvas) {
    122         // TODO 自动生成的方法存根
    123         //super.onDraw(canvas);
    124         //绘制背景图
    125         /*
    126          * backgroundBitmap:要绘制的图片
    127          * left 图片的左边界
    128          * top 图片的上边界
    129          * paint 绘制图片要使用的画笔
    130          */
    131         canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
    132         //绘制可滑动的按钮
    133         canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint);
    134     }
    135     
    136     /**
    137      * 判断是否发生拖到
    138      * 如果拖动了,就不再响应Onclick事件
    139      * true:发生拖动
    140      * false:没有发生拖动
    141      */
    142     private boolean isDrag = false;
    143 
    144     /**
    145      * onClick事件在view.onTouchEvent中被解析
    146      * 系统对Onclick事件的解析,过于简陋,只要有down事件和up事件,系统即认为发生了click事件
    147      */
    148     public void onClick(View v) {
    149         /*
    150          * 如果没有拖动,才执行改变状态的动作
    151          */
    152         if(!isDrag) {
    153             currState = ! currState;
    154             flushState();//刷新当前开关状态
    155         }
    156     }
    157 
    158     /**
    159      * 刷新当前开关视图
    160      */
    161     private void flushState() {
    162         if(currState) {
    163             slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth();
    164         }else {
    165             slidebtn_left =0;
    166         }
    167         
    168         flushView();    
    169     }
    170     
    171     public void flushView() {
    172         /**
    173          * 对slidebtn_left的值进行判断
    174          * 0 <= slidebtn_left <= backgroundwidth-slidebtnwidth(这样才能保证滑动的开关不会滑动越界)
    175          *             
    176          */
    177         int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值
    178         //确保slidebtn_left >= 0
    179         slidebtn_left =(slidebtn_left>0)?slidebtn_left:0;
    180         //确保slidebtn_left <=maxLeft
    181         slidebtn_left = (slidebtn_left<maxLeft)?slidebtn_left:maxLeft;
    182         
    183         //告诉系统我需要刷新当前视图,只要当前视图可见状态,就会调用onDraw方法重新绘制,达到刷新视图的效果
    184         invalidate();
    185     }
    186     
    187     /**
    188      * down事件时的x值
    189      */
    190     private int firstX;
    191     /**
    192      * touch事件的上一个x值
    193      */
    194     private int lastX;
    195     
    196     @Override
    197     public boolean onTouchEvent(MotionEvent event) {
    198         super.onTouchEvent(event);
    199         switch(event.getAction()) {
    200         case MotionEvent.ACTION_DOWN:
    201             firstX = lastX = (int) event.getX();
    202             isDrag = false;
    203             break;
    204         case MotionEvent.ACTION_MOVE:
    205             //判断是否发生拖动
    206             if(Math.abs(event.getX()-lastX)>5) {
    207                 isDrag = true;
    208             }
    209             
    210             //计算手指在屏幕上移动的距离
    211             int dis = (int) (event.getX()-lastX);
    212             //将本次的位置设置给lastX
    213             lastX = (int) event.getX();
    214             //根据手指移动的距离,改变slidebtn_left的值
    215             slidebtn_left = slidebtn_left+dis;
    216             break;
    217         case MotionEvent.ACTION_UP:
    218             
    219             //在发生拖动的情况下,根据最后的位置,判断当前的开关的状态
    220             if(isDrag){
    221                 int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值    
    222                 /**
    223                  * 根据slidebtn_left判断,当前应该是什么状态
    224                  * 
    225                  */
    226                 if(slidebtn_left>maxLeft/2) {//应为打开状态
    227                     currState = true;
    228                 }else {
    229                     currState = false;
    230                 }
    231                 flushState();
    232             }
    233             
    234             break;
    235         
    236         }
    237         flushView();
    238         return  true;
    239     }
    240 
    241     
    242     
    243 
    244 
    245 
    246 }

    代码逻辑如下:

    布署程序到模拟器上,如下:

  • 相关阅读:
    zoj 1239 Hanoi Tower Troubles Again!
    zoj 1221 Risk
    uva 10192 Vacation
    uva 10066 The Twin Towers
    uva 531 Compromise
    uva 103 Stacking Boxes
    稳定婚姻模型
    Ants UVA
    Golden Tiger Claw UVA
    关于upper、lower bound 的探讨
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4842166.html
Copyright © 2011-2022 走看看