• 自定义控件(视图)2期笔记07:自定义控件之 自定义属性(开关按钮案例的优化)


    1.先前,我们编好的开关按钮的项目工程,如下:

    2. 下面我们要使用自定义的属性优化这个开关按钮,如下:

    (1)第1步,我们在res/values文件夹下,新建一个attrs.xml文件,如下:

    其中attrs.xml,如下:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <resources>
     3      <!-- 声明一个属性集的名称 -->
     4     <declare-styleable name="MyToggleBtn">
     5 
     6         <!-- 声明一个属性name是my_background  类型为引用类型  引用资源ID-->
     7         <attr name="my_background" format="reference"></attr>
     8         
     9         <!-- 声明一个属性name是my_slide_btn  类型为引用类型  引用资源ID -->
    10         <attr name="my_slide_btn" format="reference"></attr>
    11         
    12         <!-- 声明一个属性name是curr_state  类型为布尔值 -->
    13         <attr name="curr_state" format="boolean"></attr>
    14     </declare-styleable>
    15     
    16 </resources>



    (2)第2步,在布局文件activity_main.xml文件中使用上面设置的属性,如下:

    (3)接下来就是来到MyToggleButton代码中,获得上面自定义的属性my_background、my_slide_btn和curr_state,如下:

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

    运行效果如下:

    3. 上面的是标准的自定义属性的使用,还有不太正规,但是很方便的使用自定义属性的方法,如下:

    (1)在上面的"开关按钮"工程的activity_main.xml文件中,添加如下:

    (2)然后在MyToggleButton.java使用在xml文件中定义的testAttrs,如下:

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

    测试logcat输出为:

  • 相关阅读:
    模块 hashlib模块
    设计模式
    类中双下方法
    工作小结 常见定制类
    python collections模块
    启动脚本
    anaconda镜像
    理解python的可变参数
    使用spark
    python 异常处理
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4847204.html
Copyright © 2020-2023  润新知