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 }

    代码逻辑如下:

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

  • 相关阅读:
    Android 解决toolbar标题不显示问题
    Android Material Design之CollapsingToolbarLayout使用
    Android ToolBar标题文字居中的方法
    Android ToolBar自定义图标,关联DrawerLayout
    Android studio 隐藏toolbar上的app title
    Android CollapsingToolbarLayout Toolbar的title覆盖问题
    K-Means算法的收敛性和如何快速收敛超大的KMeans?
    CentOS6.5下编译R源码并安装Spark R
    说说Windows7 64bits下安装TensorFlow GPU版本会遇到的一些坑
    Windows7 64bits下安装TensorFlow GPU版本(图文详解)
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4842166.html
Copyright © 2011-2022 走看看